Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Strong Skipping Mode によってrecompositionはどう変わったのか

mikan
September 25, 2024

Strong Skipping Mode によってrecompositionはどう変わったのか

DroidKaigi.onCompletion { 2024@Online }
https://yumemi.connpass.com/event/329691/

mikan

September 25, 2024
Tweet

More Decks by mikan

Other Decks in Technology

Transcript

  1. Strong Skipping Mode を有効にする 方法はいろいろある 1. compose-runtime:1.7.0 2. kotlin 2.0.20

    3. compose compiler にオプションを渡す // build.gradle.kts // 1. tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() { compilerOptions.freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true", ) } composeCompiler { // 2. before kotlin 2.0.20 enableStrongSkippingMode = true // 3. after kotlin 2.0.20 featureFlags = setOf( ComposeFeatureFlag.StrongSkipping.disabled() // 無効にする書き方
  2. 検証用コード @Composable fun Names( names: List<String>, ) { Row( horizontalArrangement

    = Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } }
  3. @Composable fun Users( unstableUserClass: UnstableUserClass, unstableUserDataClass: UnstableUserDataClass, stableUserClass: StableUserClass, immutableUserClass:

    ImmutableUserClass, stableUserDataClass: StableUserDataClass, immutableUserDataClass: ImmutableUserDataClass, names: List<String>, modifier: Modifier = Modifier, ) { Column(modifier) { User(unstableUserClass) User(unstableUserDataClass) User(stableUserClass) User(immutableUserClass) User(stableUserDataClass) User(immutableUserDataClass) Names(names) } }
  4. @Composable fun MainScreen1( count: Int, onChangeCount: (Int) -> Unit, modifier:

    Modifier = Modifier, ) { val names = mutableListOf("mikan") val unstableUserClass = UnstableUserClass(names) val unstableUserDataClass = UnstableUserDataClass(names) val stableUserClass = StableUserClass(names) val immutableUserClass = ImmutableUserClass(names) val stableUserDataClass = StableUserDataClass(names) val immutableUserDataClass = ImmutableUserDataClass(names) Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
  5. @Composable fun MainScreen2( count: Int, onChangeCount: (Int) -> Unit, modifier:

    Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // positional memoized by remember val unstableUserClass = UnstableUserClass(names) val unstableUserDataClass = UnstableUserDataClass(names) val stableUserClass = StableUserClass(names) val immutableUserClass = ImmutableUserClass(names) val stableUserDataClass = StableUserDataClass(names) val immutableUserDataClass = ImmutableUserDataClass(names) Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
  6. @Composable fun MainScreen3( count: Int, onChangeCount: (Int) -> Unit, modifier:

    Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } val unstableUserClass = remember { UnstableUserClass(names) } val unstableUserDataClass = remember { UnstableUserDataClass(names) } val stableUserClass = remember { StableUserClass(names) } val immutableUserClass = remember { ImmutableUserClass(names) } val stableUserDataClass = remember { StableUserDataClass(names) } val immutableUserDataClass = remember { ImmutableUserDataClass(names) } Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
  7. MainScreen2 (mutableListにrememberをつけたやつ) @Composable fun MainScreen2( count: Int, onChangeCount: (Int) ->

    Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュが存在するので再割り当ては発生しない val unstableUserClass = UnstableUserClass(names) // UnstableUserClass(["mikan", "mikan"]) → UnstableUserClass(["mikan", "mikan"]) val unstableUserDataClass = UnstableUserDataClass(names) // UnstableUserDataClass(["mikan", "mikan"]) → UnstableUserDataClass(["mikan", "mikan"]) val stableUserClass = StableUserClass(names) // StableUserClass(["mikan", "mikan"]) → StableUserClass(["mikan", "mikan"]) val immutableUserClass = ImmutableUserClass(names) // ImmutableUserClass(["mikan", "mikan"]) → ImmutableUserClass(["mikan", "mikan"]) val stableUserDataClass = StableUserDataClass(names) // StableUserDataClass(["mikan", "mikan"]) → StableUserDataClass(["mikan", "mikan"])
  8. MainScreen2 (mutableListにrememberをつけたやつ) @Composable fun MainScreen2( count: Int, onChangeCount: (Int) ->

    Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュが存在するので再割り当ては発生しない val unstableUserClass = UnstableUserClass(names) // skippableでないのでrecompositionする val unstableUserDataClass = UnstableUserDataClass(names) // skippableでないのでrecompositionする val stableUserClass = StableUserClass(names) // skippableだが、再割り当てによって参照が変わっているのでrecompositionする val immutableUserClass = ImmutableUserClass(names) // skippableだが、再割り当てによって参照が変わっているのでrecompositionする val stableUserDataClass = StableUserDataClass(names) // skippableであり、equals比較において同じとみなされるのでrecompositionしない
  9. MainScreen3 (変数すべてにrememberをつけたやつ) @Composable fun MainScreen3( count: Int, onChangeCount: (Int) ->

    Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserClass = remember { UnstableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserDataClass = remember { UnstableUserDataClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val stableUserClass = remember { StableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val immutableUserClass = remember { ImmutableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val stableUserDataClass = remember { StableUserDataClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない
  10. MainScreen3 (変数すべてにrememberをつけたやつ) @Composable fun MainScreen3( count: Int, onChangeCount: (Int) ->

    Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserClass = remember { UnstableUserClass(names) } // skippableでないのでrecompositionする val unstableUserDataClass = remember { UnstableUserDataClass(names) } // skippableでないのでrecompositionする val stableUserClass = remember { StableUserClass(names) } // skippableであり、再割り当ては発生していないのでスキップ val immutableUserClass = remember { ImmutableUserClass(names) } // skippableであり、再割り当ては発生していないのでスキップ val stableUserDataClass = remember { StableUserDataClass(names) } // skippableであり、再割り当ては発生していないのでスキップ
  11. Strong Skipping Mode MainScreen2 (mutableListにrememberをつけたやつ) @Stable と @Immutable をつけた data

    class および listを単純に渡しているものについてはスキップした → listはキャッシュが使われるのでスキップしたと考えられる
  12. Strong Skipping Mode 気になった点 @Composable fun Names( names: List<String>, )

    { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } } Names(listOf("mikan")) // skip
  13. まとめ Strong Skipping Mode を有効化することで、不安定な型であっても同値であればrecompositionをスキップ するようになった 公式ドキュメントには、不安定な型については === で比較するとあったが、 ==

    で比較しているように 見える これまでたまたま描画が更新できていた箇所が、Strong Skipping Mode を有効化によって更新しなくなる 可能性がある