以下の内容はhttps://kcpoipoi.hatenablog.com/entry/2018/12/16/215219より取得しました。


MVVM 完全に理解した - 10

前回は期せずして 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パターン理解できるかな?




以上の内容はhttps://kcpoipoi.hatenablog.com/entry/2018/12/16/215219より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14