Android JetpackのViewModelの使い方についてサンプルアプリを作りながら説明していきたいと思います。
ViewModelとは
ViewModelとは、Android Jetpackに含まれるコンポーネントの1つで、画面を描画する際に必要となるデータの保存や管理をおこなうためのクラスです。
ActivityやFragmentでは、例えば画面回転をすると再作成されるため、これらで管理している一時データは消えてしまいます。
しかし、ViewModelでは画面の回転などでもデータを引き継ぐことができます。
このことは、次のライフサイクルの項目を見るとわかるでしょう。
また、MVVMアーキテクチャで開発をする際のVM(View Model)の部分にあたります。
View(ActivityやFragment等々)とViewModelの関係は以下のようになります。
DatabindingやLiveDataも合わせて使用することで実現できます。
※ゆくゆくはこの作りが出来るようにしていきます。
ViewModelのライフサイクル
ViewModelのライフサイクルは以下のようになります。
※androidのdevelopersサイトからお借りしました。
ViewModelは対象のLifecycleOwnersよりも生存期間が長くなるようになっています。
ActivityやFragmentが画面回転によって再作成された場合でもViewModelは生存し続けるため、データを保持しつづけることが可能です。
開発環境
サンプルアプリ作成時の開発環境は以下です。
- macOS Catalina 10.15.2
- Android Studio 3.5
- Kotlin 1.3.61
また、今回作成したサンプルアプリのソースコードは githubのViewModelSample に置いてあります。
気になる方は動かしてみてください。
作成するViewModelサンプルアプリ
今回作成するサンプルアプリでは、FragmentとViewModelにそれぞれ数値データを持ち、「COUNT UP」ボタンをクリックすると、それぞれの数値がインクリメントされる簡単なアプリを作ります。
画面回転や画面遷移でFragmentとViewModelで管理している数値データがどのようになるのかを確認するため、3画面(First、Second、Third)用意します。
FirstとSecondのViewModelはフラグメント間でデータ共有するようにし、Thirdでは共有しないようにしたいと思います。
また、作成するサンプルアプリではNavigationも使っていますが、Navigationの使い方についてはこちらで紹介しています。
gradleに依存関係を追加
プロジェクトにViewModelを追加していきます。
プロジェクト直下の build.gradle にLifecycleのバージョン設定を追加します。
※2020/01/19 時点の最新バージョンの 2.1.0 を追加します。
buildscript {
ext {
lifecycle_version = '2.1.0'
}
}
app直下の build.gradle にViewModelの依存関係を追加します。
dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
}
※LiveDataもこれで使えるようになります。
ViewModelを追加
小さなViewModelクラスを作成します。
countプロパティを持っていて、onClickCountUpのアクションでインクリメントするだけのViewModelとします。
class SampleViewModel : ViewModel() {
var count: Int = 0
private set
fun onClickCountUp() {
count++
}
}
ViewModelインスタンスを作成
FirstとSecondのViewModelはフラグメント間でデータ共有させるため、以下のようにViewModelを作成します。
ここで大事なのが、 ViewModelProvidersに同一のFragmentActivityを渡すことです。
これをすることで、スコープがアクティビティに設定された同じインスタンスを受け取ることができます。
super.onViewCreated(view, savedInstanceState)
...
model = activity?.run {
ViewModelProviders.of(this)[SampleViewModel::class.java]
} ?: throw Exception("Invalid Activity")
val countUpButton = view.findViewById(R.id.countUpButton)
countUpButton.setOnClickListener {
count++ // Fragmentのcountをアップ
model.onClickCountUp() // ViewModelにアクションを伝える(ViewModelのcountをアップ)
updateView() // 画面のカウント数を更新
}
updateView() // 画面のカウント数を更新
}
Thirdではフラグメント間でデータ共有しないため、ViewModelの作成箇所だけ少し変えます。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
model = ViewModelProviders.of(this)[SampleViewModel::class.java]
...
}
完成版デモ
画面回転をすると、Fragmentで管理している値が0に戻っています。
これが、画面回転することによりFragmetが再作成されていることによるものです。
しかし、ViewModelの値は回転前と同じことが確認できます。
また、FirstとSecondでのViewModelの値が同一な点も注目です。
これが、Fragment間でViewModelが共有されていることによって同じ値にすることができています。
Thirdについては、共有しないようにViewModelを作成したため別の値となっています。
最後に
ViewModelコンポーネントはいかがでしたか。
Android Developersで推奨しているMVVMアーキテクチャにほんの少し近付けたと思います。
ActivityやFragmentのライフサイクルによる不具合やクラッシュを減らすこともできるので、ViewModelの動きと使い方を覚えて、プロジェクトに適用させていくと良いと思います