ViewModel の保存済み状態のモジュール Android Jetpack の一部。
UI の状態の保存で説明したように、ViewModel オブジェクトは構成の変更を処理できるため、ローテーションなどの状態を気にする必要はありません。ただし、システムによって開始されたプロセスの終了を処理する必要がある場合は、バックアップとして SavedStateHandle API を使用することをおすすめします。
UI の状態は通常、ViewModel オブジェクトに保存されるか、このオブジェクトで参照されます。そのため Compose で rememberSaveable を使用するには、保存済み状態モジュールで処理できるボイラープレートが必要です。
このモジュールを使用する場合、ViewModel オブジェクトは、コンストラクタを介して SavedStateHandle オブジェクトを受け取ります。このオブジェクトは、保存済み状態との間でオブジェクトの書き込みや取得を行えるようにする Key-Value マップです。これらの値は、システムによってプロセスが強制終了された後も保持され、同じオブジェクトを介して引き続き使用できます。
保存済み状態はタスクスタックに関連付けられます。タスクスタックがなくなると、保存済みの状態もなくなります。これは、アプリを強制停止する、[最近] メニューからアプリを削除する、デバイスを再起動するときに発生することがあります。その場合、タスクスタックが表示されなくなり、保存済みの状態の情報を復元できなくなります。ユーザーが開始する UI の状態の破棄シナリオでは、保存済みの状態は復元されません。システム開始のシナリオでは、復元されます。
設定
SavedStateHandle を使用するには、ViewModel のコンストラクタ引数として受け入れます。
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
その後、追加の構成なしで、コンポーザブル内で ViewModel のインスタンスを取得できます。デフォルトの ViewModel ファクトリにより、適切な SavedStateHandle が ViewModel に提供されます。
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
カスタム ViewModelProvider.Factory インスタンスを提供する場合、CreationExtras と viewModelFactory DSL を使用することによって SavedStateHandle の使用を有効にできます。
SavedStateHandle を使用する
SavedStateHandle クラスは、set() メソッドおよび get() メソッドを介して、保存済み状態との間でデータの書き込みや取得を行えるようにする Key-Value マップです。
SavedStateHandle を使用すると、クエリ値がプロセスの終了後も保持されるため、再作成の前と後で同じフィルタ処理が施されたデータのセットがユーザーに表示されるようになります。アクティビティやフラグメントを手動で保存、復元し、その値を ViewModel に転送して戻す必要はありません。
SavedStateHandle では、Key-Value マップの操作で役立つ可能性のあるメソッドが他にも用意されています。
contains(String key)- 指定されたキーの値の有無を確認します。remove(String key)- 指定されたキーの値を削除します。keys()-SavedStateHandle内に含まれるすべてのキーを返します。
また、オブザーバブルなデータホルダーを使用して SavedStateHandle から値を取得できます。サポートされている型は次のとおりです。
StateFlow
StateFlow オブザーバブル内にラップされている値を SavedStateHandle から取得できます。値を直接変更する必要があるかどうかに応じて、読み取り専用ストリームまたは変更可能なストリームを選択できます。
getStateFlow(): 状態を読み取るだけでよい場合は、これを使用します。SavedStateHandleの別の場所でキーの値を更新すると、StateFlow は新しい値を受け取ります。これは、読み取り専用ストリームを公開し、Flow 演算子を使用して変換する場合に最適です。getMutableStateFlow(): 読み取りアクセスと書き込みアクセスの両方が必要な場合に使用します。返されたMutableStateFlowの.valueを更新すると、基になるSavedStateHandleが自動的に更新されるため、キーを手動で設定する必要がなくなります。
これらの値は通常、データのリストをフィルタリングするクエリの入力のような、ユーザー操作により更新されます。
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
KotlinX Serialization のサポート
複雑な UI 状態の場合は、KotlinX Serialization とともに saved プロパティ デリゲートを使用できます。このデリゲートを使用すると、カスタム @Serializable データクラスを SavedStateHandle に直接永続化できます。これにより、プロセスの終了後も ViewModel の状態が保持されるため、Compose UI は再作成時に状態をシームレスに復元できます。
これを使用するには、データクラスに @Serializable アノテーションを付け、ViewModel で saved デリゲートを使用します。
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
Compose State のサポート
状態が KotlinX Serialization ではなく Compose の Saver API に依存している場合、lifecycle-viewmodel-compose アーティファクトは saveable デリゲートを提供します。これにより、SavedStateHandle と Compose の Saver の相互運用が可能になり、rememberSaveable とカスタム Saver を使って保存できる State なら、SavedStateHandle を使って保存することもできるようになります。
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
サポートされているタイプ
SavedStateHandle 内に保持されるデータは、アプリの残りの savedInstanceState とともに、Bundle として保存、復元されます。
直接サポートされている型
デフォルトでは、以下に示すように、Bundle と同じデータ型の SavedStateHandle で set() と get() を呼び出すことができます。
| 型 / クラスのサポート | 配列のサポート |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
クラスが上記のリストのいずれも拡張しない場合は、@Parcelize Kotlin アノテーションを追加するか、Parcelable を直接実装して、クラスを Parcelable にすることを検討してください。
非 Parcelable クラスを保存する
クラスが Parcelable や Serializable を実装しておらず、これらのインターフェースのいずれかを実装するように変更できない場合、そのクラスのインスタンスを SavedStateHandle に直接保存できません。
ライフサイクル 2.3.0-alpha03 以降、setSavedStateProvider() メソッドを使用して、オブジェクトを Bundle として保存 / 復元するための独自ロジックを提供することで、SavedStateHandle で任意のオブジェクトを保存できるようになりました。SavedStateRegistry.SavedStateProvider は、保存する状態を含む Bundle を返す単一の saveState() メソッドを定義するインターフェースです。SavedStateHandle は状態を保存できるようになると、saveState() を呼び出して SavedStateProvider から Bundle を取得し、関連するキーの Bundle を保存します。
ACTION_IMAGE_CAPTURE インテントを介してカメラアプリから画像をリクエストし、カメラが画像を保存する場所に関する一時ファイルを渡すアプリの例を考えます。TempFileViewModel は、この一時ファイルを作成するロジックをカプセル化します。
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
TempFileViewModel で SavedStateHandle を使用してデータを保持することで、アクティビティのプロセスの強制終了後に復元された際、一時ファイルが消失されないようにできます。TempFileViewModel でデータを保存できるようにするには、SavedStateProvider を実装し、ViewModel の SavedStateHandle でプロバイダとして設定します。
private fun File.saveTempFile() = bundleOf("path", absolutePath) class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
ユーザーが戻ったときに File データを復元するには、SavedStateHandle から temp_fileBundle を取得します。これは、絶対パスを含む saveTempFile() により提供される Bundle と同じです。この絶対パスを使用して、新しい File をインスタンス化できます。
private fun File.saveTempFile() = bundleOf("path", absolutePath) private fun Bundle.restoreTempFile() = if (containsKey("path")) { File(getString("path")) } else { null } class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { val tempFileBundle = savedStateHandle.get<Bundle>("temp_file") if (tempFileBundle != null) { tempFile = tempFileBundle.restoreTempFile() } savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
テストで SavedStateHandle を使用する
SavedStateHandle を依存関係として受け取る ViewModel をテストするには、必要なテスト値で SavedStateHandle の新しいインスタンスを作成し、テストする ViewModel インスタンスに渡します。
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
参考情報
ViewModel の保存済み状態モジュールの詳細については、以下のリソースをご覧ください。
Codelab
コンテンツを閲覧する
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- UI の状態を保存する
- 監視可能なデータ オブジェクトの使用
- 依存関係を使用して ViewModel を作成する