2024年11月14日木曜日

M3 の LinearProgressIndicator の progress は lambda になっているが、使い方に注意しないと recomposition が走ることがある

M2 の LinearProgressIndicator の progress 引数は Float でしたが、M3 では () -> Float になっています。
  1. androidx.compose.material.LinearProgressIndicator(  
  2.     progress = 0.5f,  
  3. )  
  4.   
  5. androidx.compose.material3.LinearProgressIndicator(  
  6.     progress = { 0.5f },  
  7. )  
いずれも内部の実装は Canvas composable を使って描画しています。
そのため、progress を lambda にすることで Composition と Layout phase をスキップして Drawing phase だけやり直せばよくなり、その分パフォーマンスが良くなります。
https://developer.android.com/develop/ui/compose/phases

実際以下のコードを実行して Layout Inspector で recomposition の回数を見ると、M2 の方は recompositoin されていますが M3 の方は skip されています。
  1. Column(  
  2.     verticalArrangement = Arrangement.spacedBy(16.dp),  
  3.     modifier = Modifier.fillMaxSize().padding(16.dp),  
  4. ) {  
  5.     var progress by remember { mutableFloatStateOf(0f) }  
  6.   
  7.     androidx.compose.material3.Button(  
  8.         onClick = { progress = Random.nextFloat() },  
  9.     ) {  
  10.         Text("update progress")  
  11.     }  
  12.   
  13.     androidx.compose.material.LinearProgressIndicator(  
  14.         progress = progress,  
  15.         modifier = Modifier.fillMaxWidth(),  
  16.     )  
  17.   
  18.     androidx.compose.material3.LinearProgressIndicator(  
  19.         progress = { progress },  
  20.         modifier = Modifier.fillMaxWidth(),  
  21.     )  
  22. }  



M3 の LinearProgressIndicator を wrap するときは、wrap する component でも progress を lambda で取るように注意してください(より正確に言うと、lamda の中で state から読み出しを行うようにするということ)。そうしないと M3 の LinearProgressIndicator を使っていても recompose が走ります。
  1. Column(  
  2.     verticalArrangement = Arrangement.spacedBy(16.dp),  
  3.     modifier = Modifier.fillMaxSize().padding(16.dp),  
  4. ) {  
  5.     var progress by remember { mutableFloatStateOf(0f) }  
  6.   
  7.     androidx.compose.material3.Button(  
  8.         onClick = { progress = Random.nextFloat() },  
  9.     ) {  
  10.         Text("update progress")  
  11.     }  
  12.   
  13.     LinearProgressIndicatorM2(progress)  
  14.   
  15.     LinearProgressIndicatorM3_Bad(progress)  
  16.   
  17.     LinearProgressIndicatorM3_Good({ progress })  
  18. }  
  1. @Composable  
  2. private fun LinearProgressIndicatorM2(progress: Float) {  
  3.     androidx.compose.material.LinearProgressIndicator(  
  4.         progress = progress,  
  5.         modifier = Modifier.fillMaxWidth(),  
  6.     )  
  7. }  
  8.   
  9. @Composable  
  10. private fun LinearProgressIndicatorM3_Bad(progress: Float) {  
  11.     androidx.compose.material3.LinearProgressIndicator(  
  12.         progress = { progress },  
  13.         modifier = Modifier.fillMaxWidth(),  
  14.     )  
  15. }  
  16.   
  17. @Composable  
  18. private fun LinearProgressIndicatorM3_Good(progress: () -> Float) {  
  19.     androidx.compose.material3.LinearProgressIndicator(  
  20.         progress = progress,  
  21.         modifier = Modifier.fillMaxWidth(),  
  22.     )  
  23. }  



そうは言っても、階層のどこかで progress が読み出されていることもあるでしょう(Text Composable で progress の値を表示しているとか)。その場合は rememberUpdatedState を使うことで LinearProgressIndicator の recomposition を skip させることができます。
  1. @Composable  
  2. private fun Wrap(progress: Float, onUpdate: () -> Unit) {  
  3.     val updatedProgress by rememberUpdatedState(progress)  
  4.     Column(  
  5.         verticalArrangement = Arrangement.spacedBy(16.dp),  
  6.         modifier = Modifier.fillMaxSize().padding(16.dp),  
  7.     ) {  
  8.   
  9.         ...  
  10.   
  11.         LinearProgressIndicatorM3_Good({ updatedProgress })  
  12.     }  
  13. }  

0 件のコメント:

コメントを投稿