Androidアプリを設計する上で考慮したポイント
この記事はAndroid Advent Calendar 2017の13日目です。
僕が今業務で開発しているAndroidアプリの設計の紹介と、そこに行き着くまでの僕の設計に対する考え方を紹介します。
こんな設計にしています
どういうポイントを考慮してなぜこの形に行き着いたのかを以下紹介します。
ビューとモデルのライフサイクルの違いに対応したい
Androidアプリにおいて、アプリのプロセスを通して保持したい情報がある一方、画面の状態などはユーザーの操作によって簡単に破棄されることがあります。つまり、モデルは長命だが、ビューやビューモデルは短命ということです。
この辺はこちらのスライドが神資料なので是非読んだことない人は読んでみるとよいと思います。少し古い資料ですが、十分に今でも通用する考え方がわかると思います。
では、長命のモデルから短命のビューに結果を返すときはどうすればよいでしょうか?
結論から言えば、ビューとモデルの境界にはRxJavaを採用しています。理由は、ビューが死んだときの非同期処理を中止するといったハンドリングが簡単だからです。そして、ビューとモデルの間にRxを採用するなら、モデルの内部もRxに統一した方が見やすいため、アプリ全体でRxを採用しました。 今ならArchitecture ComponentsのLiveDataで統一するのもアリだとは思いますが、非同期処理の結合周り(直列処理、並列処理)などはRxの方が向いている気もしています。
依存関係をシンプルに保ちたい
大事なのは依存関係を一方向にすることです。自分より上の層に依存するのは当然NGですが、自分と同じレイヤーにも絶対に依存してはいけません。すぐに循環参照が発生してカオスになるからです。
この設計の場合、ビュー、ビューモデル、モデルが一方向の依存になります。さらに、モデルはユースケース(ビジネスロジック)、リポジトリ(CRUDを責務とする層)、データ(APIやDAO)の3層構造にしています。
なお、Clean Architectureも検討したことはありますが、現状そこまで厳密にはやっていません。なぜなら、Clean Architectureを実現しようとした場合、タマネギの内側から外側へアクセスさせるためにインタフェースを用意するのがめんどくさかったからです。
この依存関係を一方向に保つためのツールとして、DI(Dagger)を使うのはおすすめです。循環参照のような上記のルールを守らない依存関係を作ろうとすると、コンパイル時にエラーにしてくれます。
同じコードを二度書きたくない
この設計の中で、ユースケース層は果たして本当に必要なのかと聞かれることがあります。ビューモデルが直接リポジトリを呼んでいいんじゃないかと。
答えはアプリごとに違うと思いますが、私の現在の環境ではユースケースはあったほうが良いと思います。例えば、ログインの直後にプッシュ通知のトークンを登録するなど、複数のリポジトリを一連の流れで呼ぶことがあります。ログイン処理をただ1つの画面からしか呼ばないのであればよいですが、複数の画面でログイン可能な仕様の場合、この一連の処理をまとめておくことで同じ処理を二度実装しなくてよくなります。
データバインディングもビューの処理を共通化するのに役立ちます。Glideを使った画像の読み込みなど、ほとんど同じ処理を画面ごとに描くのではなく、BindingAdapterを使ってコードは共通化し、XMLで定義できるようにします。
まとめ
以上まとめますと、以下のような点を考慮して今の設計に行き着きました。
- ビューはすぐに死ぬ前提で考える
- 依存関係をシンプルにする
- できるだけコードを共通化する
何がベストな設計かはプロジェクトやチームによって異なるとは思いますが、是非参考にしてください。