Composableの状態
Composable関数には3つの状態があります。
State1 | State2 | State3 | |
skippable | ○ | ||
restartable | ○ | ○ |
skippableは引数を比較し、同じであればrecomposeをskipできます。skippableである場合は必ずrestartableです。
restartableはrecomposeのスコープとなる事ができます。restartableであればそのComposable単体でRecomposeできますが、restartableでなければ、更に上位のスコープでRecomposeを行います。
これだけ聞くと、全てskippableにしておけば良いように思えますが、ドキュメントには必ずしもComposableをSkippableにしなければいけないわけではないと記述があります。
@NonRestartableComposable
restartableであり、skippableでない関数は、以下のようにする事を「検討しても良い」とあります。
今回はNonRestartableにする方を掘り下げていきます。
- Composableの引数をStableにしてskippableにする
- NonRestartableにする
挙動確認
@NonRestartableComposable
があるComposable関数と、無い関数で動作の違いを見ていきます。
Button1とButton2があり、それぞれカウントアップするボタンがあります。
@Composable public fun Test( modifier: Modifier = Modifier, ) { var count1 by remember { mutableStateOf(0) } var count2 by remember { mutableStateOf(0) } Column(modifier = modifier) { Button(onClick = { count1++ }) { Text(text = "Button1") } Button(onClick = { count2++ }) { Text(text = "Button2") } NonRestartable("1", count1) Restartable("1", count1) NonRestartable("2", count2) Restartable("2", count2) } } @Composable @NonRestartableComposable public fun NonRestartable(tag: String, count: Int) { Log.d("LOG", "Restartable: $tag, count=$count") Text(text = "$tag: count=$count") } @Composable public fun Restartable(tag: String, count: Int) { Log.d("LOG", "Restartable: $tag, count=$count") Text(text = "$tag: count=$count") }
Button1を押したときの出力
Non-Restartable: 1, count=1 Restartable: 1, count=1 Non-Restartable: 2, count=0
text2の方の値は変わっていないはずですし、StringはStableなので、Skipされるはずですが、 @NonRestartableComposable
の方は呼ばれています。
挙動としては inline
関数に近いのかなと思っています。
公式で使用されている部分
ドキュメントには、再構成のRootになる可能性が低く、機能が殆どなく、引数を読み取らずに他のComposableな関数に渡す関数に使うと良いと記述してあります。
1.1.0時点では LaunchedEffect
や SideEffect
に使用されています。
Compose1.2.0では他にも使用されている部分が増えています。
※1.2.0-beta2での情報です
Spacer
内部でPainterが引数のImageを呼び出している、引数がImageVectorの方のImage。
Surfaceを呼び出しているだけのCard
@NonRestartableComposableをどう使っていくべきか
これに関してはパフォーマンスに大きく問題が出るという事は無いと思うので、付けなくても、条件を満たしたら付けても良いと思います。ライブラリ開発者以外はそこあまで気にしなくてもいいかなととも思いますが、小さいパーツのちょっとした共通化に使うのがいいかなと思います。
例えば以下のようなコードです。(Painterはunstable)
@Composable @NonRestartableComposable private fun Budge( modifier: Modifier, text: String, color: Color, icon: Painter, // unstable contentDescription: String, ) { Icon( modifier = modifier .size(32.dp) .clip(RoundedCornerShape(2.dp)) .background(color) .padding(2.dp), painter = icon, contentDescription = contentDescription, tint = Color.White, ) Spacer(modifier = Modifier.width(8.dp)) Text( text = text, color = colorResource(id = R.color.navy_70), fontSize = 18.sp, ) }
P.S.
この内容で登壇しました。
https://andpad.connpass.com/event/249842/