Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Strong Skipping Mode によってrecompositionはどう変わったのか
Search
mikan
September 25, 2024
Technology
410
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Strong Skipping Mode によってrecompositionはどう変わったのか
DroidKaigi.onCompletion { 2024@Online }
https://yumemi.connpass.com/event/329691/
mikan
September 25, 2024
More Decks by mikan
See All by mikan
Lazy APIを使ってGradleビルド速度を改善する
mikanichinose
1
75
Navigation3でViewModelにデータを渡す方法
mikanichinose
0
700
「脳に収まるコードの書き方」を読んで学んだこと
mikanichinose
1
220
RepositoryのSSoT化
mikanichinose
0
94
Kotlin Multiplatform 始めました
mikanichinose
1
150
Web APIをなぜつくるのか
mikanichinose
0
3.9k
イベントをどう管理するか
mikanichinose
3
410
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
510
Modeling UiEvent
mikanichinose
0
140
Other Decks in Technology
See All in Technology
ループエンジニアリングでE2Eテストを実践
noriyukitakei
0
270
NDIAS CTF 2026 問題解説会資料
bata_24
0
180
AIに障害切り分けを全部やってもらった。 。 。 。
estie
0
360
AI時代のエンジニアキャリアについて今一度考える
sakamoto_582
1
1.2k
10x Speed With QA Agent Platform - How we scaled adoption from individual effort to organizational capability
lycorptech_jp
PRO
0
130
「ちゃんとやっている」は独りよがりだった ― 不安に寄り添うインシデント対応へ / Towards incident response that addresses anxieties
chmikata
1
520
MySQL & MySQL HeatWave Report - June 2026
freshdaz
0
310
Amazon EVS で VCF 9.0 / 9.1 のサポート開始まとめ
mtoyoda
0
200
美しいコードを書くためにF#を学んでみた話
yud0uhu
1
180
AWS Summit の片隅で、体育座りしながらコミュニティがにぎわう理由を考えた
k_adachi_01
2
350
事業価値を⽣み出すSREへ SREが担うべき意思決定の5層
kenta_hi
0
170
金融の未来を考える / Thinking About the Future of Finance
ks91
PRO
0
150
Featured
See All Featured
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
220
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Into the Great Unknown - MozCon
thekraken
41
2.6k
The Curse of the Amulet
leimatthew05
2
13k
How STYLIGHT went responsive
nonsquared
100
6.2k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
210
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
300
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
340
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
240
How GitHub (no longer) Works
holman
316
150k
Transcript
Strong Skipping Mode によって recompositionはどう変わったの か DroidKaigi.onCompletion { 2024@Online }
mikan(一瀬喜弘)
自己紹介
目的 Strong Skipping Mode を有効にすることで、不安定なパラメ ータに依存するComposable関数のrecompositionに、どのよ うな違いが生じるのか検証してみた 注意 コード例は検証用のものなので、およそプロダクションコー ドで利用するようなものにはなってません
Strong Skipping Mode とは TL;DR 不安定なパラメータに依存しているComposable関数も skippableがつくようになった
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() // 無効にする書き方
検証用コード @Composable fun Names( names: List<String>, ) { Row( horizontalArrangement
= Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } }
検証用コード class UnstableUserClass( val names: List<String>, ) @Composable fun User(user:
UnstableUserClass) { Names(user.names) }
検証用コード data class UnstableUserDataClass( val names: List<String>, ) @Composable fun
User(user: UnstableUserDataClass) { Names(user.names) }
検証用コード @Stable class StableUserClass( val names: List<String>, ) @Composable fun
User(user: StableUserClass) { Names(user.names) }
検証用コード @Immutable class ImmutableUserClass( val names: List<String>, ) @Composable fun
User(user: ImmutableUserClass) { Names(user.names) }
検証用コード @Stable data class StableUserDataClass( val names: List<String>, ) @Composable
fun User(user: StableUserDataClass) { Names(user.names) }
検証用コード @Immutable data class ImmutableUserDataClass( val names: List<String>, ) @Composable
fun User(user: ImmutableUserDataClass) { Names(user.names) }
@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) } }
@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, ) } }
@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, ) } }
@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, ) } }
MainScreen1 すべてrecompositionした。recompositionの前後で見た目は変わらず → recompositionの度にすべての変数が再割り当てされているから → StableUserDataClassとImmutableUserDataClassがrecompositionした理由について、この説明だと違和感 が残る
MainScreen2 (mutableListにrememberをつけたやつ) @Stable と @Immutable を つけた data class だけスキップした。
recompositionした ものは 描画が 更新された
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"])
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しない
MainScreen3 (変数すべてにrememberをつけたやつ) @Stable と @Immutable をつけたものについてはスキップした。recompositionしたものは描画が更新された
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時: キャッシュされているため再割り当ては発生しない
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であり、再割り当ては発生していないのでスキップ
Strong Skipping Mode MainScreen1 すべてrecompositionした: 変化なし
Strong Skipping Mode MainScreen2 (mutableListにrememberをつけたやつ) @Stable と @Immutable をつけた data
class および listを単純に渡しているものについてはスキップした → listはキャッシュが使われるのでスキップしたと考えられる
Strong Skipping Mode MainScreen3 (変数すべてにrememberをつけたやつ) すべてスキップした → すべてskippableになり、再割り当ては発生していないのでスキップしたものと考えられる
Strong Skipping Mode 気になった点 @Composable fun Names( names: List<String>, )
{ Row( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } } Names(listOf("mikan")) // skip
まとめ Strong Skipping Mode を有効化することで、不安定な型であっても同値であればrecompositionをスキップ するようになった 公式ドキュメントには、不安定な型については === で比較するとあったが、 ==
で比較しているように 見える これまでたまたま描画が更新できていた箇所が、Strong Skipping Mode を有効化によって更新しなくなる 可能性がある
ご清聴ありがとうございました