🐻‍❄️

Jetpack Compose の remember と remeberSaveable の動作を比べる

2021/11/13に公開

remember とは

  • remember を利用するとコンポーザブル関数で特定の値を保持できる。
  • 保持した値はコンポーザブル関数が再コンポーズされたときにのみ取得できる。
  • もし Activity が破棄された場合には remember で保持した値は破棄される

rememberの動作を確認する

PLUS ボタンと MINUS ボタンを押した回数を rememberで値を保持するようにしてみた。

MainState: Counter 状態を保持するクラス

private val mainState = MainState()

class MainState {
    private val _count: MutableStateFlow<Int> = MutableStateFlow(0)
    val count: StateFlow<Int> = _count

    fun plus() {
        _count.value = _count.value + 1
    }

    fun minus() {
        _count.value = _count.value - 1
    }
}

MainActivity: Counter コンポーザブルを配置するクラス

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val count by mainState.count.collectAsState()
            SampleTheme {
                Counter(count = count, onPlus = { mainState.plus() }, onMinus = { mainState.minus() })
            }
        }
    }
}

Counter: remember で値を保持するコンポーザブル関数

@Composable
private fun Counter(count: Int, onPlus: () -> Unit, onMinus: () -> Unit) {
    var plusCount by remember { mutableStateOf(0) }
    var minusCount by remember { mutableStateOf(0) }

    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.Center)
        ) {
            Text(text = "Counter", fontSize = 20.sp)
            Text(text = "Count $count PlusCount $plusCount MinusCount $minusCount")
            Button(
                onClick = {
                    plusCount++
                    onPlus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Plus")
            }
            Button(
                onClick = {
                    minusCount++
                    onMinus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Minus")
            }
        }
    }
}

再コンポーズされても値が保持される

PLUS ボタンや MINUS ボタンを押すと MainState に保持されているカウント値が更新され Counter コンポーザブルが再コンポーズされます。PUS ボタンや MINUS ボタンを押した回数は remember で値を保持しているので再コンポーズされても値が保持されリセットされません。

Sep-02-2021 20-04-21.gif

Activity が破棄されると値は破棄される

開発者向けオプションで「アクティビティを保持しない」オプションを ON にして画面を再表示してみる。すると remember で保持した値は Activity を破棄されるので PLUS ボタンや MINUS ボタンのカウントは 0 になる。

Sep-02-2021 20-08-13.gif

rememberSaveable とは

  • 基本的な動作は remember 同じ動作をする。
  • もし Activity が破棄された場合にでも rememberSaveableで保持した値は破棄されない

rememberSaveable の動作を確認する

remember の動作確認で利用したコードを変更して PLUS ボタンと MINUS ボタンを押した回数を rememberSaveable で値を保持するようにしてみた。

Counter: rememberSaveableで値を保持するコンポーザブル関数

@Composable
private fun Counter(count: Int, onPlus: () -> Unit, onMinus: () -> Unit) {
    var plusCount by rememberSaveable { mutableStateOf(0) }
    var minusCount by rememberSaveable { mutableStateOf(0) }

    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.Center)
        ) {
            Text(text = "Counter", fontSize = 20.sp)
            Text(text = "Count $count PlusCount $plusCount MinusCount $minusCount")
            Button(
                onClick = {
                    plusCount++
                    onPlus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Plus")
            }
            Button(
                onClick = {
                    minusCount++
                    onMinus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Minus")
            }
        }
    }
}

再コンポーズされても値が保持される

remember と同じで rememberSaveable でも再コンポーズされても値が保持される。

Sep-02-2021 20-19-14.gif

Activity が破棄されても値が保持される

remember の動作確認と同じ手順で開発者向けオプションで「アクティビティを保持しない」オプションを ON にして画面を再表示してみる。すると remember では破棄されていた値が rememberSaveable では保持されている。

Sep-02-2021 20-19-45.gif

rememberrememberSaveable でも非表示なったら値は保持できない

公式ドキュメントでも記載があるが remember や rememberSaveable では再コンポーズされた場合にのみ値を保持するらしい。以下のように特定のカウントに到達したら非表示にするコードを用意して再コンポーズされないケースの動作を確かめてみる。

MainActivity: 10を超えたら他のコンポーザブル関数を呼ぶ

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val count by mainState.count.collectAsState()

            SampleTheme {
                if (10 < count) {
                    CounterOver10(count, { mainState.plus() }, { mainState.minus() })
                } else {
                    CounterLessThan10(count, { mainState.plus() }, { mainState.minus() })
                }
            }
        }
    }
}

CounterOver10: 10を超えた時に呼び出されるコンポーザブル関数

@Composable
private fun CounterOver10(count: Int, onPlus: () -> Unit, onMinus: () -> Unit) {
    var plusCount by remember { mutableStateOf(0) }
    var minusCount by remember { mutableStateOf(0) }

    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.Center)
        ) {
            Text(text = "CounterOver10", fontSize = 20.sp)
            Text(text = "Count $count PlusCount $plusCount MinusCount $minusCount")
            Button(
                onClick = {
                    plusCount++
                    onPlus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Plus")
            }
            Button(
                onClick = {
                    minusCount++
                    onMinus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Minus")
            }
        }
    }
}

CounterLessThan10: 10時未満の時に呼び出されるコンポーザブル関数

@Composable
private fun CounterLessThan10(count: Int, onPlus: () -> Unit, onMinus: () -> Unit) {
    var plusCount by remember { mutableStateOf(0) }
    var minusCount by remember { mutableStateOf(0) }

    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.Center)
        ) {
            Text(text = "CounterLessThan10", fontSize = 20.sp)
            Text(text = "Count $count PlusCount $plusCount MinusCount $minusCount")
            Button(
                onClick = {
                    plusCount++
                    onPlus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Plus")
            }
            Button(
                onClick = {
                    minusCount++
                    onMinus.invoke()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "Minus")
            }
        }
    }
}

やはり一度非表示になると値は保持できない

カウント値が10を超えると呼び出されるコンポーザブル関数が切り替わる。そして再度カウント値を10未満にしたらもとのコンポーザブル関数が呼び出されるが remember で保持した値は破棄される。

Sep-02-2021 20-40-26.gif

Discussion