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

Debugging Jetpack Compose

Debugging Jetpack Compose

2023년 7월 14일 코엑스 인터콘티넨탈 파르나스에서 열린 <2023 대한민국 인공지능위크> 모바일 트랙에서 발표한 내용입니다. 2023 Google I/O에서 공개된 동명의 세션을 기반으로 Jetpack Compose 디버깅 도구와 사례를 소개합니다.

[링크]
Google I/O 세션: https://www.youtube.com/watch?v=Kp-aiSU8qCU
블로그 : https://workspace-dev.medium.com
HOLIX Jetpack Compose 사용자 모임 : https://hlx.kr/workspace

workspace

July 14, 2023
Tweet

More Decks by workspace

Other Decks in Programming

Transcript

  1. Agenda • Jetpack Compose Debugging Tools • Debugging Practice •

    Debugging Mindset • Common Issues • recomposition • jank • tracing • Tips • Summary
  2. 1. Layout Inspector ઱۽ ޙઁܳ ੋ૑ ೞחؘ ࢎਊ • Recomposition

    Count • Recomposition Skip Count • Recomposition Highlight * Android Studio Hedgehogࠗఠ э਷ చীࢲ Device ചݶҗ Layout Inspectorܳ ೣԋ ࢎਊೡ ࣻ ੓਺ (Embedded Layout Inspector)
  3. Types of recomposition Unskippable recomposition Indirect recomposition A parent composable

    got recomposed and this caused parameters to change Direct recomposition A compose state object inside the composable changed.
  4. @Composable fun DirectRecomposition() { var count by remember { mutableStateOf(0)

    } Text("$count") Button(onClick = { count++ }) { Text("Increment") } }
  5. @Composable fun IndirectRecomposition() { var count by remember { mutableStateOf(0)

    } val doubled = count * 2 MyText(doubled) Button(onClick = { count++ }) { Text("Increment") } } @Composable fun MyText(count: Int) { Text("$count") }
  6. @Composable fun UnstableRecomposition() { var count by remember { mutableStateOf(0)

    } Text("$count") var list by remember { mutableStateOf(listOf(1, 2, 3)) } MyList(list) Button(onClick = { count++ }) { Text("Increment") } } @Composable fun MyList(list: List<Int>) { /**/ }
  7. Recomposition state • Unchanged - ߸҃੉ হ਺ • Changed -

    ߸҃੉ ੌযթ • Uncertain • Static - ߸҃੉ হਸ Ѫ੉ۄ ౸ױؽ • Unstable
  8. @Composable fun IndirectRecomposition() { var count by remember { mutableStateOf(0)

    } val doubled = count * 2 MyText(doubled) Button(onClick = { count++ }) { Text("Increment") } } @Composable fun MyText(count: Int) { Text("$count") }
  9. @Composable fun UnstableRecomposition() { var count by remember { mutableStateOf(0)

    } Text("$count") var list by remember { mutableStateOf(listOf(1, 2, 3)) } MyList(list) Button(onClick = { count++ }) { Text("Increment") } } @Composable fun MyList(list: List<Int>) { /**/ }
  10. 3. Logs ੣਷ Recomposition੉ ߊࢤೞח ࢚ടীࢲ જ਷ ؀উ झ௼܀੉ա গפݫ੉࣌੉

    ੌযզ ٸ ч੉ ࡅܰѱ ߸ೞݴ recomposeغחؘ, breakpointܳ ੟ই ݒ ೐ۨ੐݃׮ чਸ ࠁח Ѫ਷ য۰਑
  11. @Composable fun ScrollingList() { val scrollState = rememberLazyListState() var count

    by remember { mutableStateOf(0) } Log.d("ScrollingList", "${scrollState.firstVisibleItemIndex}") Button(onClick = { count++ }) { Text(text = "Increment") } Text(text = "count: $count") LazyColumn( modifier = Modifier.fillMaxSize(), state = scrollState, ) { items(100) { Text(text = "index $it") } } }
  12. @Composable fun ScrollingList() { val scrollState = rememberLazyListState() var count

    by remember { mutableStateOf(0) } Log.d("ScrollingList", "${scrollState.firstVisibleItemIndex}") Button(onClick = { count++ }) { Text(text = "Increment") } Text(text = "count: $count") LazyColumn( modifier = Modifier.fillMaxSize(), state = scrollState, ) { items(100) { Text(text = "index $it") } } }
  13. @Composable fun ScrollingList() { val scrollState = rememberLazyListState() var count

    by remember { mutableStateOf(0) } SideEffect { Log.d("ScrollingList", "${scrollState.firstVisibleItemIndex}") } Button(onClick = { count++ }) { Text(text = "Increment") } Text(text = "count: $count") LazyColumn( modifier = Modifier.fillMaxSize(), state = scrollState, ) { items(100) { Text(text = "index $it") } } }
  14. 4. Visual Lint 22֙ I/Oীࢲ ୭ୡ ҕѐ ׼दী View दझమ݅

    ੸ਊ. Android Studio Hedgehogীࢲ Compose Previewী ؀ೠ ૑ਗ द੘🎉
  15. 5. Tracing (Profiler) System trace ծ਷ overhead दр ஏ੿੉ ਊ੉ೣ

    ݃ఊػ ੉߮౟݅ਸ ୶੸ Method trace ֫਷ overhead ݽٚ method callਸ ࠁৈષ
  16. ٣ߡӦ਷ ޙઁо ޖ঺ੋ૑ ੿੄ೞח Ѫীࢲ द੘ • যڌѱ ز੘ೞח Ѫ੉ۄ

    ৘࢚೮ա? • ؀न যڃ ੌ੉ ੌযաҊ ੓ա? • ৵ Ӓۧѱ ز੘ೡ Ѫ੉ۄ ࢤп೮ա? • ׼नҗ زܐٜ਷ যڃ о੿ਸ ೞҊ ੓ա? Define Reproduce Validate Assumptions Fix What is the problem?
  17. ੤അ оמೠ ৘दܳ ҳഅ • ৘࢚чҗ पઁч, ഑਷ ৘࢚ ز੘җ

    पઁ ز੘ਸ ࠺Үೞח పझ౟ ੘ࢿ • (TIP) ੘਷ playground ೐۽ં౟ ഑਷ ݽٕਸ ٜ݅যفݶ ಞೞ׮. Define Reproduce Validate Assumptions Fix Reproduce the problem
  18. ޙઁܳ ੤അೡ ࣻ ੓ਵפ بҳܳ ੉ਊೞৈ оࢸਸ Ѩૐ • ޙઁী

    ٮۄ ب਑ਸ ߉ਸ ࣻ ੓ח ׮নೠ بҳٜ੉ ੓਺ Define Reproduce Validate Assumptions Fix Validate Assumptions
  19. FollowedPodcastCarouselItem( … lastEpisodeDate = lastEpisodeDate, … ) @Composable private fun

    FollowedPodcastCarouselItem( … lastEpisodeDate: OffsetDateTime? = null, … ) { … if (lastEpisodeDate != null) { Text( text = lastEpisodeDate?.let { lastUpdated(it) }, … ) } … }
  20. FollowedPodcastCarouselItem( … lastEpisodeDateText = lastEpisodeDate?.let { lastUpdated(it) }, … )

    @Composable private fun FollowedPodcastCarouselItem( … lastEpisodeDateText: String? = null, … ) { … if (lastEpisodeDateText != null) { Text( text = lastEpisodeDateText, … ) } … }
  21. x

  22. fun ProvideAndroidCompositionLocals( owner: AndroidComposeView, content: @Composable () -> Unit )

    { val view = owner val context = view.context …… val uriHandler = remember { AndroidUriHandler(context) } val saveableStateRegistry = remember { DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner) } …… val imageVectorCache = obtainImageVectorCache(context, configuration) }
  23. Setup // build.gradle (root) subprojects { tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { compilerOptions {

    if (project.findProperty("composeCompilerReports") == "true") { freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.buildDir.absolutePath + “/compose_compiler”, ) } if (project.findProperty("composeCompilerMetrics") == "true") { freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.buildDir.absolutePath + “/compose_compiler” ) } } } }
  24. Build # 1. local.properties, gradle.properties ١ীࢲ property ࢸ੿ composeCompilerReports=true composeCompilerMetrics=true

    # 2. terminalীࢲ gradle task प೯ द property ࢸ੿ ➜ ./gradlew -PcomposeCompilerReports=true -PcomposeCompilerMetrics=true assembleDebug
  25. Top Level Metrics (-module.json) { "skippableComposables": 16, "restartableComposables": 20, "readonlyComposables":

    0, "totalComposables": 20, "restartGroups": 20, "totalGroups": 20, "staticArguments": 21, "certainArguments": 1, "knownStableArguments": 276, "knownUnstableArguments": 1, "unknownStableArguments": 0, "totalArguments": 277, "markedStableClasses": 0, "inferredStableClasses": 3, "inferredUnstableClasses": 2, "inferredUncertainClasses": 0, "effectivelyStableClasses": 3, "totalClasses": 5, "memoizedLambdas": 15, "singletonLambdas": 1, "singletonComposableLambdas": 9, "composableLambdas": 9, "totalLambdas": 15 }
  26. Composable Signatures (-composables.txt) restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun Greeting( stable name:

    String stable modifier: Modifier? = @static Companion ) restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun GreetingPreview() restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DirectRecomposition() restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun IndirectRecomposition() restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MyText( stable count: Int ) restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ScrollingList() restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun UnstableRecomposition() restartable scheme("[androidx.compose.ui.UiComposable]") fun MyList( unstable list: List<Int> )
  27. classes.txt data class StableClass( val stableProperty1: String, val stableProperty2: Int,

    val stableProperty3: Long, val stableProperty4: Double ) data class StableNestedClass( val stableClass: StableClass ) data class UnstableClass( val stableProperty1: String, val stableProperty2: Int, val stableProperty3: Long, val unstableProperty1: List<Int>, // Date, Map, Set... ) data class UnstableNestedClass( val unstableClass: UnstableClass ) stable class StableClass { stable val stableProperty1: String stable val stableProperty2: Int stable val stableProperty3: Long stable val stableProperty4: Double <runtime stability> = Stable } stable class StableNestedClass { stable val stableClass: StableClass <runtime stability> = Stable } unstable class UnstableClass { stable val stableProperty1: String stable val stableProperty2: Int stable val stableProperty3: Long unstable val unstableProperty1: List<Int> <runtime stability> = Unstable } unstable class UnstableNestedClass { unstable val unstableClass: UnstableClass <runtime stability> = Unstable }
  28. ⚠ ݣ౭ ݽٕਸ ॳҊ ҅द׮ݶ? ݣ౭ ݽٕ ೐۽ં౟ীࢲ domainҗ э਷

    ࣽࣻ kotlin module੄ ௿ېझח 
 Compose compilerо unstable۽ р઱ 3rd party ۄ੉࠳۞ܻܳ ࢎਊೡ ٸب ਬ੄೧ঠ… 1. Compose runtime dependencyܳ ୶оೞѢա 2. UI moduleীࢲ ui model۽ mapping ژח wrapper۽ ੜ хऱࢲ ࢎਊ
  29. ݽٚ Composable਷ Skippable੉যঠ? ইפ׮. skippable۽ ݅٘ח ࣻҊо प૕੸ੋ о஖ࠁ׮ ੸਷

    ҃਋ ޖद • ੗઱ recomposeо غ૑ ঋח composable (৘: ചݶ ױਤ) • ױࣽ൤ ղࠗীࢲ skippableೠ composableਸ о૑Ҋ ੓ח ҃਋
  30. 1. ޙઁ ߊࢤ ߑ૑ೞӝ • Lint۽ ੜޅػ ز੘ਸ ঠӝೞח ௏٘ܳ

    ੘ࢿೞ૑ ঋب۾ ೞ੗ • Twitter compose rules • Visual Lint • Compose Compiler Reportsܳ ా೧ ਫ਼੤੸ਵ۽ ޙઁо ੓ח class, composableਸ ଺੗.
  31. ز੘ী ޙઁо ੓ਸ ٸ ؀୊ߨ • Layout Inspectorܳ ా೧ ࠛ೙ਃೠ

    recompositionਸ ଺ইࠁ੗. • Composable functionী breakpointܳ Ѧয ٣ߡѢীࢲ Recomposition Stateܳ ഛੋೡ ࣻ ੓׮. • झ௼܀, গפݫ੉࣌ ١ ࠼بо ੣਷ recomposition੉ ߊࢤೡ ٸח Logܳ ࢎਊೞ੗.
  32. Resources • https://goo.gle/jetcaster-pager • https://goo.gle/compose-stability-explained • https://goo.gle/baseline-profiles • https://goo.gle/compose-tracing •

    https://medium.com/androiddevelopers/jetpack-compose-debugging-recomposition-bfcf4a6f8d37 • https://medium.com/androiddevelopers/jetpack-compose-composition-tracing-9ec2b3aea535 • https://youtube.com/watch?v=Kp-aiSU8qCU
  33. Q&A Q. Composable ௏٘ ܻ࠭ ౲੉ ੓ਸөਃ? A. ୊਺ী ٣੗ੋ

    दझమਸ উ੟Ҋ ௏٘ܳ ૢ ٸח ઺ࠂغח ஹನք౟ب ݆Ҋ modifierо ؋૑؋૑ ׳ܻחѱ ࠁӝب উજওणפ׮. ஹನ੷࠶ ௏٘о ݠ݁ࣘীࢲ ੜ উӒ۰૑ח ҃਋о ݆ওחؘ ٣੗ੋ दझమ ҳഅਸ ೞݶࢲ оةࢿ੉ ѐ ࢶغ঻؍ ҃೷੉ ੓णפ׮. ௏٘ ܻ࠭ ౲਷ ইק Ѫ э૑݅, ઁ ҃೷ਸ ݈ॹܻ٘੗ݶ ҳഅ ࢎ೦ਸ যڌѱ ҳഅೡ૑ ҳ࢚ਸ alignೞח োणਸ ૓೯೮ णפ׮. Composeח ࢜۽਍ ӝࣿ੉׮ࠁפ п੗ ѐੋٜ੄ ࣼ۲ب৬ ૑ध Ѻରо ߥয૕ ࣻ ੓חؘ, ੷ח ಕয ೐۽Ӓ ې߁, VOD, ࢎղ ࣁ޷աܳ ా೧ ࢜۽਍ ૑धਸ ੹౵೮णפ׮. ੉۠ җ੿ਸ ా೧ ௏٘ ܻ࠭ ੉੹ী ࢎҊߑधਸ ੌ஖द ெ ௏٘ ܻ࠭ ബਯਸ ֫ੌ ࣻ ੓঻णפ׮.
  34. Q&A Q. Compose internals ъ੄? ଼?ਸ ࠁݶ Compose Compilerо ঌইࢲ

    ೧઻ࢲ @Stable, @Immutable annotationਸ ׮ח Ѫਸ ௼ѱ न҃ ॳ૑ ঋইب ػ׮Ҋ غয੓חؘ যڌѱ ࢤпೞदաਃ? (૕ޙ ӝরաח؀۽ ੸যࠌ חؘ ݏѷભ…?) A. Ӓ ޙ੢ ੹റ۽ যڃ ղਊ੉ ੓ח૑ ઁо ੍যࠁ૑ ঋইࢲ ૒੽੸ਵ۽ ׹ਸ ೧ܻ٘ӟ য۰਎ Ѫ эणפ׮. ׮݅, @Stableҗ @Immutable੄ ৉ೡҗ ӝמਸ ੿ഛ൤ ইन׮ݶ ੷ח ԙ ೙ਃೞ׮Ҋ ࢤп೤פ׮. @Stableҗ @Immutable annotation੄ ഝਊ਷ ৉द Compose ղࠗ ௏٘ܳ ଵઑೞदݶ જणפ׮. Color, Dp৬ э਷ Ѫٜ ࠗఠ द੘ೞৈ material, material3١ componentٜী ٜযоח ੋ੗ٜ੄ ҳഅਸ ా೧ annotation੄ ৉ೡਸ ഛੋ ೞप ࣻ ੓ਸ Ѫ эणפ׮. ੷ח ৮੹ ୡӝী ੗ܐо ցޖ ࠗ઒ೞ؍ द੺ ଵҊೡ݅ೠѱ ղࠗ ௏٘ ߆ী হ঻חؘ, ૑Әب stabilityী ؀ೠ ӝળਸ ࣁ਋ח ਬബೠ ೟णߑध੉ۄ ࢤп೤פ׮.