【Kotlin/Android】SavedStateHandleの使い方!ViewModelの状態管理と復元

【Kotlin/Android】SavedStateHandleの使い方!ViewModelの状態管理と復元

この記事からわかること

  • Kotlin/AndroidSavedStateHandle使い方
  • 状態保存仕組み

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

AndroidアプリのUI状態の保存

公式リファレンス:UI の状態を保存する

Androidは画面の回転など特定の動作などによりシステムによってActivityやアプリが破棄されデータが消失してしまうことが多く発生します。そのためUIの状態を一時的に保存して復元するような仕組みを構築しておくことがユーザーエクスペリエンスを維持する上で重要になってきます。

UIの状態を一時的に保存するための仕組みは以下のようにいくつか用意されていますがユースケースに応じて最適なものを取捨選択する必要があります。

※ Saved instance stateは基本的にユーザーの入力やナビゲーションに依存する一時的な状態などを保持するために使用します。そのため軽量なデータのみとし大量のデータの場合はPersistent storage(永続化ストレージ)を使用することが推奨されています。Saved instance stateは以下の種類があります。

  1. Jetpack Compose: rememberSaveable・・・Compose用
  2. View: onSaveInstanceState() API・・・主にUIロジックを保存
  3. ViewModel: SavedStateHandle・・・主にビジネスロジックを保存

ユーザーの期待値との乖離

実際にユーザーの期待とアプリの挙動が乖離を生まないようにするためにはユーザーの期待値とシステムの挙動を理解しておく必要があります。例えば以下のような動作を行った場合にはアプリはクリーンな状態から起動され直されることが期待値になり、以下の挙動ではシステムの挙動も一致してUIの状態をクリーンにします。

ユーザーがActivityを完全に終了する方法

しかしユーザーがクリーンになることを期待しない場合でもシステムがUIの状態を破棄してしまうケースが存在します。

期待値に反してシステムがUIの状態を破棄してしまうケース

これに応じたそれぞれの対応の違いは以下になります。

ViewModel Saved instance state Persistent storage(永続ストレージ)
保存先 メモリ内 メモリ内 ディスクまたはネットワーク上
構成変更後も保持
システムによるプロセス終了後も保持 ×
ユーザーによるアクティビティ終了/onFinish() も保持 × ×
データの上限 複雑なオブジェクトは問題ないが、使用可能なメモリによってスペースが制限される プリミティブ型と String などのシンプルな小さなオブジェクトのみ ネットワーク リソースからのディスク スペースまたはネットワークからの取得コスト / 時間によってのみ制限される
読み取り / 書き込み時間 高速(メモリアクセスのみ) 低速(シリアル化 / シリアル化解除が必要) 低速(ディスク アクセスまたはネットワーク トランザクションが必要)

今回はその中の1つである「Saved instance state」の「ViewModel: SavedStateHandle」の使い方についてまとめていきます。

SavedStateHandleの導入

公式リファレンス:ViewModel の保存済み状態モジュール

SavedStateHandleViewModelの中で使用できる「Saved instance state」です。ViewModelだけでも構成の変更には耐え得る構造を実装することができますが、システムによるプロセス終了後の場合は値が消失してしまいます。これを防ぐためにViewModel内ではSavedStateHandleが用意されています。

SavedStateHandleViewModel継承したクラスのコンストラクタの引数として受け取ることが可能です。導入に必要な記述はこれのみでインスタンス化時などは特に意識する必要はありません。

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

使い方

SavedStateHandleデータの取得・書込が行えるKey-Valueのマップ構造です。内部的な挙動はSavedInstanceStateなどと同じでBundleを使用してデータを保持しています。

データの取得・保存はset/getメソッドを使用します。

val id = savedStateHandle.get<String>("id")
savedStateHandle.set("id", userId)

またキーを直接指定することも可能です。

val id = savedStateHandle["id"] ?: ""
savedStateHandle["id"] = userId

復帰した際にシームレスに変更を通知するための手段としてLiveDataStateFlowとの連携も可能になっています。

val filteredData: LiveData<List<String>> =
    savedStateHandle.getLiveData<String>("query").switchMap { query ->
    repository.getFilteredData(query)
}

val filteredData: StateFlow<List<String>> =
    savedStateHandle.getStateFlow<String>("query")
        .flatMapLatest { query ->
            repository.getFilteredData(query)
        }

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index