こんにちは、 id:numanuma08 です。Google I/O が盛り上がっていますね。GCPやFlutter、Androidで色々なアップデートが発表されてとてもワクワクします。
今日はAndroidネタと言うことで、androidx.lifecycle
の2.5.0から追加されるSavedStateHandle.savable
を紹介します。
SavedStateHandleとは
Jetpack Composeを使ってViewModelで画面の状態を管理する場合を考えます。SavedStateHandleを使うとフォーム画面などでフォーム画面などでユーザーが入力したデータを保持が可能です。入力中にユーザーが別の操作を行ってアプリがバックグラウンドに移動しても、復帰したあとで入力中のデータを使って入力の再開が可能になります。今までSavedStateHandleを使うとき、例えば次のように実装をしていました。
class FormViewModel( private val savedStateHandle: SavedStateHandle ) : ViewModel() { companion object { const val STATE_KEY_TEXT = "text" } var text: String? by mutableStateOf(savedStateHandle[STATE_KEY_TEXT]) private set fun onChangeText(text: String) { this.text = text savedStateHandle[STATE_KEY_TEXT] = text } }
内部的にMutableState
を利用したいのでこうなっていますが、キーを自前で管理している以上利用するキーを間違えてバグを生む可能性があります。
class FormViewModel( private val savedStateHandle: SavedStateHandle ) : ViewModel() { companion object { const val STATE_KEY_TEXT = "text" const val STATE_KEY_NAME = "name" } var text: String? by mutableStateOf(savedStateHandle[STATE_KEY_TEXT]) private set var name: String? by mutableStateOf(savedStateHandle[STATE_KEY_NAME]) private set fun onChangeName(text: String) { this.name = name // キーを間違えている。STATE_KEY_NAME が正しい savedStateHandle[STATE_KEY_TEXT] = text } }
テストも書くことで検知できるミスかも知れませんが、そもそも仕組み上このようなミスが発生しないと嬉しいですよね。
SavedStateHandle.savableを使う
lifecycle 2.5.0-alpha06からsaveable
という拡張関数が追加されました。
https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ja#2.5.0-alpha06
saveableはdelegated propertyとなっていて、次の機能を提供します。
- rememberSaveableと同じように画面の状態をSavedStateHandleを使って保持する
- プロパティを定義するときにSavedStateHandleのキーも一緒に定義する
とくに2番めの「プロパティを定義するときにSavedStateHandleのキーも一緒に定義する」によってキーを間違えるミスが起きにくくなります 🎉
先程のサンプルコードは次のようになります。
class FormViewModel( private val savedStateHandle: SavedStateHandle ) : ViewModel() { var text: String? by savedStateHandle.saveable("text") { mutableStateOf(null) } private set var name: String? by savedStateHandle.saveable("name") { mutableStateOf(null) } private set fun onChangeText(text: String) { this.text = text } fun onChangeName(text: String) { this.name = name } }
SavedStateHandle周りのコードが消えてスッキリしました。非nullなプロパティであればプロパティの名前をキーにする拡張関数も利用できます。
var loading: Boolean by savedStateHandle.saveable { mutableStateOf(false) }
注意点
この記事を執筆した時点でlifecycle 2.5.0 はまだrcなので、今後APIの変更があるかもしれません。
まとめ
SavedStateHandle.saveableを紹介しました。SavedStateHandle関連のコードを削減してバグの少ない実装が可能になります。