SavedStateHandle. saveableが便利そう

こんにちは、 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関連のコードを削減してバグの少ない実装が可能になります。