Android Jetpack の LiveData と ViewModel
今回から Android Jetpack に関する記事を何回かにわけて公開します。まずは Architecture Components として提供されていた LiveData と ViewModel について解説します。
LiveData とは
LiveData とはデータの入れ物で、中身を監視(observe)することができます。監視する際、アクティビティやフラグメントといった LifecycleOwner を一緒に渡すことで、UI に反映すべき時のみ中身の変更通知を行ってくれたり、不要になった時に監視をキャンセルしてくれたりします。 その他のメリットは公式ドキュメント を参照してください。
ViewModel とは
ViewModel はアクティビティやフラグメントに対するデータ置き場の役割を担います。画面回転やウィンドウのサイズが変更されると、アクティビティやフラグメントインスタンスは再生成されますが、この ViewModel はインスタンスが保持されます。通常は先ほど紹介した LiveData インスタンスを ViewModel で保持します。
プロジェクトに LiveData と ViewModel を追加する
LiveData と ViewModel は Jetpack のライブラリとして提供されています。どちらも androidx.appcompat の依存ライブラリとなっているので、追加の作業は不要です。
(本来は Kotlin 用拡張(ktx)の追加が必要なのですが、どうも Navigation の ktx 経由で追加されているようです。本記事は Navigation も追加されているものとして説明を続けます。)
今回のサンプルアプリ
LiveData と ViewModel の使い方を体験するためのサンプルとして、テキストを入力し Submit ボタンを押すとテキストが表示されるアプリを作ってみます。
ViewModel クラスを作る
まずは LiveData 置き場となる ViewModel クラスを作ります。フラグメント毎に作ることになるので、今回は MainViewModel としました。
class MainViewModel: ViewModel() {
// まだ、からっぽ
}
MutableLiveData を持たせる
今回の画面にはデータとして名前があるので、それを保持するための LiveData を ViewModel に持たせます。データをセットし、監視している人にデータが届く方式でよいので、MutableLiveData とします。
class MainViewModel: ViewModel() {
val name = MutableLiveData<String>()
}
MainViewModel を取ってくる
次に、フラグメント側で MainViewModel
を取ってくるコードを書きます。Kotlin の場合は次のようにby viewModels()
をつけるだけであとはいい感じにしてくれます。
class MainFragment : Fragment() {
private val mainVM: MainViewModel by viewModels()
// 中略
}
なお、Architecture Components が登場したときは
ViewModelProviders.of(this).get(MainViewModel::class.java)
で ViewModel オブジェクトを取得していましたが、いつの間にか
ViewModelProviders クラスはなくなっています。上記のように
by viewModels()
で取得するようにしましょう(Java
の場合はどうするのが正解なのでしょうか。。。)。
余談:ViewBinding を使う
Android Studio 3.6 ベータあたりから ViewBinding が使えるようになっています。build.gradle の android の中に次の記述を加えるだけで使えるようになります。
android {
...
viewBinding {
enabled = true
}
}
有効にすると、データバインディングの時と同様にレイアウト XML
名を使ったクラスが自動生成されます。今回は main_fragment.xml
としたので、MainFragmentBinding
というクラスを使います。
class MainFragment : Fragment() {
private val mainVM: MainViewModel by viewModels()
private lateinit var binding: MainFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
this.binding = MainFragmentBinding.inflate(inflater, container, false)
return this.binding.root
}
}
LiveData を監視する
ここまできたら後は監視し、変更があったら View
に反映させるだけです。onViewCreated()
で次のように
observe
を呼びます。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainVM.name.observe(this, Observer {
// itに最新のnameの値が入っている
binding.resultText.text = getString(R.string.result_message, it)
})
}
observe()
の第1引数には LifecycleOwner
としてフラグメントを渡しています。これで、値の変更はフラグメントがアクティブな時のみ行われ、
バックボタンなどでフラグメントが破棄されたときは監視の終了処理が自動で行われます。
MutableLiveData に値をいれる
最後に、ボタンをタップしたら入力内容を MutableLiveData にいれる部分です。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
this.binding = MainFragmentBinding.inflate(inflater, container, false)
// ボタンがタップされたら、値をMutableLiveDataにいれる
this.binding.submitButton.setOnClickListener {
mainVM.name.value = binding.nameEdit.text.toString()
binding.nameEdit.setText("")
}
return this.binding.root
}
なお、ワーカースレッドでの処理結果を MutableLiveData
に入れたい場合は次のように postValue()
を使用します。
mainVM.name.postValue(binding.nameEdit.text.toString())
まとめ
LiveData と ViewModel の使い方について簡単に説明しました。定番のパターンとして使えるようになっておきましょう。