[Android] 008. Jetpack Compose の状態

チュートリアルのJetpack Compose の状態がわかりやすいかも
チュートリアルの内容をざっくり簡略化して確認
Flow / LiveDataは別途確認

状態の仕組みについて

動作しない例

TemplateのGreeting()を流用して以下のようなボタンを押すとラベルが変わる実装をします
ただしこの実装ではラベルは何も変わりません
var nameがComposeの状態変更の検出対象になってないからです

@Composable fun AppContent() {
    var name = "Android"

    MaterialTheme {
        Surface {
            Column {
                Button(onClick = { name = "test" }) {
                    Text("click")
                }
                Greeting(text)
            }
        }
    }
}

State

State<T>にすることでComposeの状態変更の検出対象にすることができます

val name: MutableState<String> = mutableStateOf("Android")

@Composable fun AppContent() {
    MaterialTheme {
        Surface {
            Column {
                Button(onClick = { name.value = "test" }) {
                    Text("click")
                }
                Greeting(name.value)
            }
        }
    }
}

by(delegate property)を使用すればvalueではなく直接代入/参照できます
getValue/setValueのimportが必要です

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

var name by mutableStateOf("Android")

@Composable fun AppContent() {
    MaterialTheme {
        Surface {
            Column {
                Button(onClick = { name = "test" }) {
                    Text("click")
                }
                Greeting(name)
            }
        }
    }
}

remember

rememberを使用すると@Composable内部でのみ使用可能となりパフォーマンスが改善されます
詳しくは以下を参照
https://developer.android.com/jetpack/compose/performance?hl=ja

@Composable fun AppContent() {
    var name by remember { mutableStateOf("Android") }
    ...
}

rememberSaveable

rememberの場合Android実機側で自動回転をONにして回転させるとActivityが再作成されるため値が元に戻ります
rememberSaveableを使用することで対応できます
※ rememberを使用してない場合もrememberSaveable同様に値が保持されてます
※ rememberSaveableの場合でもアプリ終了時後は値は保持されてません

@Composable fun AppContent() {
    var name by rememberSaveable { mutableStateOf("Android") }
    ...
}

複数のメソッドをまたぐ場合(状態ホイスティング)

複数のメソッドをまたぐ場合以下のような実装はできません

@Composable fun AppContent() {
    var name by remember { mutableStateOf("Android") }

    MaterialTheme {
        Screen(name)
    }
}

@Composable fun Screen(name: String) {
    Surface {
        Column {
            //Error: nameはvalのため代入できない
            Button(onClick = { name = "test" }) {
                Text("click")
            }
            Greeting(name)
        }
    }
}

状態ホイスティングを使用して実装します

@Composable fun AppContent() {
    var name by remember { mutableStateOf("Android") }

    MaterialTheme {
        Screen(name) {
            name = it
        }
    }
}

@Composable fun Screen(name: String, onClick: (String) -> Unit) {
    Surface {
        Column {
            Button(onClick = { onClick("test") }) {
                Text("click")
            }
            Greeting(name)
        }
    }
}

クラスを渡す場合(状態ホルダー)

クラスを渡す場合以下のような実装ではComposeの状態変更の検出対象にすることができません
※ datasにDatasクラスを渡せば検出されますが使い勝手が悪すぎる

class Datas (var name: String, var num: Int = 0)

@Composable fun AppContent() {
    val datas by remember { mutableStateOf(Datas("Android")) }
    ...
}

以下のように状態ホルダーを作成して対応します
詳しくはチュートリアルのJetpack Compose の高度な状態と副作用を参照

class Datas {
    var name by mutableStateOf("")
    var num = 0

    constructor(name: String, num: Int) {
        this.name = name
        this.num = num
    }
}
@Composable fun rememberDatas(name: String, num: Int = 0): Datas = remember { Datas(name, num) }

@Composable fun AppContent() {
    val datas: Datas = rememberDatas("Android")
    ...
}

rememberSaveableの場合は置換えただけでは不十分でカスタムのセーバーの作成が必要です

class Datas {
    var name by mutableStateOf("")
    var num = 0

    constructor(name: String, num: Int = 0) {
        this.name = name
        this.num = num
    }

    companion object {
        val Saver: Saver<Datas, *> = listSaver(
            save = { listOf(it.name, it.num) },
            restore = {
                Datas(name = it[0] as String, num = it[1] as Int)
            }
        )
    }
}
@Composable fun rememberDatas(name: String): Datas = rememberSaveable(saver = Datas.Saver) { Datas(name) }

@Composable fun AppContent() {
    val datas: Datas = rememberDatas("Android")
    ...
}

Android Studio Dolphin 2021.3.1 Patch 1 built on September 30, 2022