前回は期せずして MVVM っぽく ViewModel を介してイベントをやり取りする実装を載せることができました。
View と ViewModel との関係はそこそこ書いてきたと思うので、今回は Model を見ていきます。
Model とは
wikiる。
アプリケーションのドメイン(問題領域)を担う、そのアプリケーションが扱う領域のデータと手続き(ビジネスロジック - ショッピングの合計額や送料を計算するなど)を表現する要素である。 Model View ViewModel - Wikipedia
日本語でおk、と思ったら一言で表した文がありました。
一般的にModelはドメインを担当すると言われるがこの言葉だけをもってModelの役割を想像するのは難しい。(中略)
Modelの役割は、後述するViewとViewModelの役割以外の部分と考えるのが妥当である。
これまで散々参考にしてきた Android Architecture BluePrints (以下AAB) においては、TasksRepository というクラスが該当しますかね。間違っていたらすみません。
TasksRepository とは
TasksRepository クラスは、ViewModel や他のクラスと Database との仲介を行うクラスです。
TasksRepository は次の TasksDataSource インターフェースを実装します。
interface TasksDataSource { fun getTasks fun saveTask fun completeTask ... //などなど }
さて、このTasksRepositoryがどこで使われるのかを見てみます。
どこで使われている?
ViewModel の中で使われます。インスタンスはコンストラクタで渡されています。
class TasksViewModel( context: Application, private val tasksRepository: TasksRepository ) : AndroidViewModel(context) { ... fun completeTask(...){ ... tasksRepository.completeTask(task) ... } ... }
コンストラクタでtasksRepositoryが渡されていますが、こいつはいったいどこで生成されているのか……ここが一番の謎でした。
ViewModel 生成の少し詳細な流れ
拡張関数
ここからは少しごちゃごちゃします。
まず、これまで ViewModel は自前で Activity に実装したMainActivity#obtainViewModel(Class<T>)にて生成していました。
Kotlin の拡張関数の機能を使って、AppCompatActivityにこのメソッドを埋め込みます。
//トップレベル等に記載。 //AABではAppCompatActivityExt.ktに記載している。 fun <T : ViewModel> AppCompatActivity.obtainViewModel(viewModelClass: Class<T>) = ViewModelProviders.of(this, ViewModelFactory.getInstance(application)).get(viewModelClass)
これで、たとえば AppCompatActivity を継承している MainActivity 内では、this.obtainViewModelといったようにメソッドを呼ぶことができます。
ViewModel の生成
さて MVVM 完全に理解した - 7 - 日々是好日 において、ViewModel は次のように生成すると書きました。
val viewmodel = ViewModelProviders.of(this).get(MainViewModel::class.java)
拡張関数との大きな違いは、of(this, ViewModelFactory.getInstance(application))となっているところです。
この ViewModelFactory を見てみます。
ViewModelFactory
ViewModelFactory.getInstanceの実装は次のとおり。
private var INSTANCE: ViewModelFactory? = null fun getInstance(application: Application) = INSTANCE ?: synchronized(ViewModelFactory::class.java) { INSTANCE ?: ViewModelFactory(application, Injection.provideTasksRepository(application.applicationContext)) .also { INSTANCE = it } }
エルビス演算子?:はあまり気にせず、synchronizedも排他制御が必要なんだなー程度で。
ViewModelFactory(application, Injection.provideTasksRepository(...))
Injection.provideTasksRepositoryで TasksRepository インスタンスをセットした ViewModelFactory を生成しています。
Injection については次回…?
とりあえず、これでofメソッドから ViewModelFactory インスタンスがセットされた ViewModelProvider が返るので、ViewModelProvider#getを実行します。
getメソッドの内部では、ViewModelFactory#create(viewModelClass)が実行され、TasksRepositoryがセットされた ViewModel が生成されます。
override fun <T : ViewModel> create(modelClass: Class<T>) = with(modelClass) { when { ... isAssignableFrom(TasksViewModel::class.java) -> //インスタンスを生成して返す TasksViewModel(application, tasksRepository) else -> ... } } as T
簡単(?)なフロー
AppCompatActivity#obtainViewModel をコール
↓
ViewModelProviders.of(this, ViewModelFactory.getInstance(application)).get(viewModelClass) で ViewModel 生成
↓
ViewModelFactory 内で TasksRepository インスタンスを生成
↓
TasksRepostitory がセットされた ViewModel を取得
TasksRepository
TasksRepositoryは直接データベースにアクセスせず、コンストラクタで与えられた TasksDataSource を経由します。
class TaskRepository ( val tasksRemoteDataSource: TasksDataSource, val tasksLocalDataSource: TasksDataSource ) : TasksDataSource { fun completeTask(task: Task) { ... tasksLocalDataSource.completeTask(...) } }
たとえば上記のcompleteTaskメソッドでは、TasksLocalDataSource#completeTaskをコールしており、処理を TasksLocalDataSource クラスで行っています。
そして TasksLocalDataSource 内では、AAC の Room クラスを使用してデータ永続化を行っており……いやそもそもtasksLocalDataSourceってどうコンストラクタに渡してるの……とかもう沼。この辺でやめておく( ˘ω˘)スヤァ
所感
調べるほど沼←
しかもあまり TasksRepository の中身の話はしていないという。
流れは分かったけど、持つべき責務ごとにクラス分け、さらにテスト性まで考慮しているので読み解くのがしんどかった。どこをどう分けてるからテストしやすいとか全然わからないけど!!
ViewModelFactory を定義して ViewModel を生成するのは常識なんですかね……。
次は Injection クラスについて書くかも。DIパターン理解できるかな?