Sunflowerリポジトリで学ぶJetPack〜ViewModel編


新型コロナの影響で自宅待機になってしまい、その間勉強するものとして`Sunflower`リポジトリを
勧めてもらいました。

JetPackのライブラリのうち、今回は`ViewModel`編です
[DataBinding編](https://qiita.com/apimama/items/87da362855a9ca74e167)、[LiveData編](https://qiita.com/apimama/items/7f53ddc9bbd9fdf305b8)などでも、
チラチラ出てきてはいましたが、見てみぬふりをしていました笑

尚、引用しているソースは明記しているところ以外は、基本的には全て`Sunflower`のリポジトリのものです。　

# 環境
* 確認時は`Android Studio`のバージョンは `3.6.2`を使用しました
* `JetPack`は`AndroidX`ライブラリを利用するので`Compile SDK`を`２８`以上にする必要があります

# そもそも`ViewModel`ってなに？
公式の説明によると

* `ViewModel` は、`ライフサイクル`を意識した方法で UI 関連のデータを保存および管理するためのクラスです
* `ViewModel` クラスを使用すると、画面の回転などの設定の変更後にデータを引き継ぐことができます。

です！

# `ViewModel`ってなに？（詳細）
公式にはこんな感じのことが書いてありました。

* **UI コントローラで利用するデータをバンドルから復元して利用しないで済む**
    * アクティビティが`onSaveInstanceState()` メソッドを使用して `onCreate()` のバンドルからデータを復元しなくてもよくなります。
    * そもそも`この方法(onSaveInstanceState()利用）`が適しているのは少量のデータの場合だけです。  
    * ユーザーやビットマップのリストのようにデータの量が多くなる可能性がある場合には適していません。 

画面をぐるぐるする度に、バンドルからデータをとって。。。とするのはめんどくさかったので、
それをしないで済むのは良さそうですね！

* **メモリリークの心配がない**
    * UI コントローラでは実行に時間がかかる非同期呼び出しを頻繁に行う必要があります。それらを管理し、破棄された後にシステムが呼び出しのクリーンアップを行ってメモリリークが発生しないようにする必要がありますが、`ViewModel`を利用した場合それをする必要がありません。

画面のライフサイクルに合わせて、データの破棄や再取得を考えて設計・実装するのは大変ですね。。。
それをしないで済むのはこちらも良さそうです！

* **リソースの無駄がない**
    * この管理ではメンテナンスを何度も実施する必要があります
    * 設定の変更によってオブジェクトが再作成された場合にはすでに行った呼び出しを再度行わなければならないこともあるため、リソースが無駄になります。

メモリリークしない作りにするために、冗長な処理になってしまいがちだったかもしれません。
それをしないで済むのはこちらもありがたいです！

* **UIコントローラクラスの肥大化防止とテスト効率の向上**
    * UI コントローラに対してデータベースやネットワークからのデータ読み込みも行うよう要求すると、クラスが肥大化することになります
    * UI コントローラに過度の役割を割り当てると、アプリの作業を他のクラスに任せずに 1 つのクラスですべて処理しようとすることになり、テストも困難になります。
    * ビューデータの所有権を UI コントローラのロジックから切り離すことで、複雑さが軽減され、効率性が高まります。

処理が増えてくると、`Activity`や`Fragment`が巨大化してしまい、本当のところ実際にUIをコントロールしているところが
見えにくくなってしまう、という状況になってしまいがちだと思います。
また、処理とUIコントロールの部分が分割できるよう、設計を工夫してなんとかしていたと思いますが、
公式でサポートする`ViewModel`で出来るのは頼もしいですね！！！

# `ViewModel`クラス
その名の通り、上記のようなUIコントローラー向けにUIデータを準備するための`ViewModel`ヘルパークラスが用意されています！！！
それを使えば上記の問題が解決できるはずです！

# 使用箇所
`Sunflower`リポジトリの中ではどのように`ViewModel`が使われているか、実際に見てみましょう

## `ViewModel`の定義
```GardenPlantingListViewModel.kt
class GardenPlantingListViewModel internal constructor(
    gardenPlantingRepository: GardenPlantingRepository
) : ViewModel() {
    val plantAndGardenPlantings: LiveData<List<PlantAndGardenPlantings>> =
            gardenPlantingRepository.getPlantedGardens()
}
```

* `plantAndGardenPlantings`を取得する処理は、アクティビティやフラグメントではなく、`GardenPlantingListViewModel`内に割り当てられてられています。
* ViewModel内で定義しているデータは`LiveData`となっています
    * こうすると更新を監視できて、非アクティブになった時に破棄できるなどメリットがあるため、一緒に使う場合が多いと思います。
    * ※詳しくは[LiveData編](http://google.co.jp)も併せて確認してください。

* ちなみに、ここでは`plantAndGardenPlantings`の値自体の取得処理は、`GardenPlantingRepository`を利用し抽象化されており、さらに外部でインスタンス化されたものをコンストラクターで渡されています。（依存性注入）
    * こうすることで、取得先がローカルDBからサーバーに変わったとしても、このクラスとの依存関係がないため、変更は不要であり、変更に強い設計になっています。
    * 尚、依存性注入については`Dagger2`を利用して実施する場合が多いですが、`Sunflowerリポジトリ`では、`Dagger2`の依存性注入については、対応しない！と明言しています笑
   
## `ViewModel`利用箇所
`UI コントローラー`(ここでは`Fragment`)ではこのようにして、`ViewModel`を利用していました。

```kotlin:GardenFragment.kt
class GardenFragment : Fragment() {
          :
          :
    private val viewModel: GardenPlantingListViewModel by viewModels {
        InjectorUtils.provideGardenPlantingListViewModelFactory(requireContext())
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentGardenBinding.inflate(inflater, container, false)
          :
          :
        subscribeUi(adapter, binding)
        return binding.root
    }

    private fun subscribeUi(adapter: GardenPlantingAdapter, binding: FragmentGardenBinding) {
    viewModel.plantAndGardenPlantings.observe(viewLifecycleOwner) { result ->
        binding.hasPlantings = !result.isNullOrEmpty()
        adapter.submitList(result)
    }
```
* `viewModel`を定義している部分ですが、`by viewModels {ラムダ式}`となっています。
    * これは`Android KTX`の機能のうち、`Properties Delegete`というもので、この場合は下記のパターンです。
    * →ラムダ式の中に自作の`Factory Method`を書くことができ、そこで作られたインスタンスを`ViewModel`に与えることができます
    
```kotlin
class MyFragment : Fragment() {
    val viewmodel: MYViewModel by viewmodels { myFactory }
}
```


ここでは、`InjectorUtils#provideGardenPlantingListViewModelFactory()`メソッドを実施し、この`ViewModel`のインスタンスを取得しています

* `viewModel.plantAndGardenPlantings` は`LiveData`のため、`observe（）`して変更を監視します。
* この処理は`Fragment`が作成された時にコールされる処理、`onCreateView()`メソッド内で実施しています。
* 値がセットされる度に`onChange()`メソッドがコールされ、ラムダ式内の処理が実施されます。そこで、UIの更新を実施しています。

### `ViewModel`と`Factory`メソッドについて
`ViewModel`の`Factory`メソッドには`androidx.lifecycle.ViewModelProvider`を使用します。

#### 引数無しの場合
```kotlin 
val viewmodel: MYViewModel by viewmodels{ 
    ViewModelProvider.NewInstanceFactory().create(MYViewModel::class.java)
}
```
これでOKです。(新たなクラスの定義は不要です。）
#### 引数ありの場合（今回のパターン）
ただし、今回は引数がある場合なので、
引数がある`ViewModelFactory`クラスを作成しています。

こちらは`ViewModelFactory`クラスの`Factory`メソッド(`Factoryクラス`のコンストラクターメソッド)を呼び出している処理です。
`Sunflower`リポジトリでは、`kotlin:InjectorUtils`クラスに各`ViewModel`の`Factory`メソッドをまとめて記載してあります。

```kotlin:InjectorUtils.kt
object InjectorUtils {
                :
                :
    fun provideGardenPlantingListViewModelFactory(
        context: Context
    ): GardenPlantingListViewModelFactory {
        val repository = getGardenPlantingRepository(context)
        return GardenPlantingListViewModelFactory(repository)
    }
                :
                :
}
```
続いて、`GardenPlantingListViewModel`の`Factory`クラスである`kotlin:GardenPlantingListViewModelFactory`を見てみましょう

```kotlin:GardenPlantingListViewModelFactory.kt
class GardenPlantingListViewModelFactory(
    private val repository: GardenPlantingRepository
) : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return GardenPlantingListViewModel(repository) as T
    }
}
```
`GardenPlantingRepository`を引数として渡す形で定義されています。

`Sunflower`リポジトリの他の`ViewModel`クラスも同じ形式となっていたので、別途作成する場合もこの形式を踏襲し、以下の部分の変更のみすればよさそうです

* クラス名(◯◯◯Factory)
* `Constructor`の引数
* オーバーライドする`create`メソッドが返す引数(新たに作成した`ViewModel`のクラス名)


# `ViewModel`とテストについて
* `ViewModel`のライフサイクルは長めに設計されています。そのため、`ViewModel`のテストをしやすい設計となっています。

![viewmodel-lifecycle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/615702/6bbd706f-5fb4-4ea2-0561-c7d2a227b950.png)

* 画面が無くなった（`Activity＃onDestroy()`)**「後」**に、なにかやる、なども`ViewmodelScope.onCleared()`とかでできます。
* この図はActivityについて書かれてますが、`Fragment`などでも基本的には同じのようです

# まとめ

* ViewModel は、ライフサイクルを意識した方法で UI 関連のデータを保存および管理するためのクラスで、画面の回転などの設定の変更後にデータを引き継ぐことができる。
* UIコントローラー(`Activity`,`Fragment`など)とライフサイクルを共にするため、メモリリークの心配やリソースの無駄がない
* UIコントローラーから`ViewModel`を切り出すことにより、UIコントローラーの肥大化を防止し、テストをしやすくすることができる
* `Android KTX`の`Properties Delegete`で`Factory`メソッドでインスンタンスを作ることができる。
* `ViewModel`クラスのコンストラクタが、`引数なし`の場合は自前の`Factory`クラスを作らないでもよいが、`引数あり`の場合は`Factory`クラスを作る必要がある。

以上です！

# 参考サイト
* [Sunflowerリポジトリ](https://github.com/android/sunflower)
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel): 公式ドキュメント
* [LiveData](https://developer.android.com/topic/libraries/architecture/livedata): 公式ドキュメント
* [DataBinding](https://developer.android.com/topic/libraries/data-binding): 公式ドキュメント
* [Android-KTX](https://qiita.com/mangano-ito/items/9b067916d1374d66b750): @mangano-ito さんの記事
* [Android-KTX　Fragment.app](https://developer.android.com/reference/kotlin/androidx/fragment/app/package-summary?hl=ja):公式ドキュメント