$30 off During Our Annual Pro Sale. View Details »

Droidcon SF: What does Recomposition mean to your app?

Droidcon SF: What does Recomposition mean to your app?

PRESENTED AT:
Droidcon San Francisco 2022

https://www.sf.droidcon.com/speaker/aida-issayeva/what-does-recomposition-mean-to-your-app%3F

DATE:
Jun 3, 2022

TIME:
13:20 > 40 min

DESCRIPTION:
You’ve heard a lot that Jetpack Compose is a declarative UI toolkit and it recomposes only the components that changed. But what does it exactly mean? How does it apply not only in the scale of one composable function but the entire app? Well, it turns out there is more than meets the eye when it comes to recomposition, and in this talk, we will deep dive into its benefits, costs, and gotchas. You’ll learn some practical tips to efficiently build functions in Jetpack Compose.

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

Aida Issayeva

June 03, 2022
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. What does Recomposition mean to your app? Aida Issayeva

  2. About me 🤖 Android GDE 󰠁 Sr. Software Engineer 󰠅

    Instructor 🐦 @aida_isay 🌐 cupsofcode.com 📽 @aida_isay
  3. TODAY’S AGENDA 02 What is Recomposition? 03 Smart Recomposition 04

    Tools to detect & debug 05 Practical tips 01 Deep into the foundation 06 Jetpack Compose Paradigm
  4. Jetpack Compose paradigm UI widgets are functions They are stateless

    Different arguments, passed to the functions, trigger updates
  5. SlotTable Android OS Composer Applier UI Node Tree State Snapshot

    System Change List Applier
  6. Deep into the foundation

  7. 02 Deep into the foundation Compose Phases 1 Composition Calls

    composable functions and creates a description of the UI 2 Layout Measures the layout elements and their children and places them on the screen 3 Drawing Draws the UI elements to the screen
  8. 02 Deep into the foundation Compose Layers UI Compiler Runtime

    1 Composition 2 Layout 3 Drawing
  9. 02 Deep into the foundation Compose Layers UI Compiler Runtime

    1 Composition 2 Layout 3 Drawing
  10. From Android OS: • Context • Configuration • Lifecycle •

    Keyboard state • SavedStateInstance • etc CompositionLocals 02 Deep into the foundation Entrance to Jetpack Compose class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... setContent { Card() } } }
  11. 02 Deep into the foundation Compose Layers UI Compiler Runtime

    1 Composition 2 Layout 3 Drawing
  12. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list
  13. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list
  14. is the scheduler for performing recomposition and applying updates to

    one or more Compositions. Recomposer
  15. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list WindowRecomposer: LifecycleAware recomposer
  16. Android Lifecycle & Compose viewTreeLifecycleOwner .lifecycle.addObserver( object : LifecycleEventObserver {

    override fun onStateChanged (lifecycleOwner: LifecycleOwner , event: Lifecycle .Event) { val self = this when (event) { Lifecycle.Event.ON_CREATE -> runRecomposeScope .launch(start = CoroutineStart .UNDISPATCHED) { try { recomposer.runRecomposeAndApplyChanges() } finally { lifecycleOwner .lifecycle.removeObserver( self) } } Lifecycle .Event.ON_START -> pausableClock ?.resume() Lifecycle .Event.ON_STOP -> pausableClock ?.pause() Lifecycle.Event.ON_DESTROY -> { recomposer.cancel() } Lifecycle .Event.ON_PAUSE -> { // Nothing } Lifecycle .Event.ON_RESUME -> { // Nothing } Lifecycle .Event.ON_ANY -> { // Nothing } } } } ) 02 Deep into the foundation Android OS State Snapshot System
  17. State<String>(“Android”) State Snapshot System @Composable fun StateSnapshotSystem() { val name

    = remember { mutableStateOf(Android) } val isImportant = remember { mutableStateOf(false) } Column { Checkbox(checked = isImportant.value, onCheckedChange = { isImportant.value = !isImportant.value }) Text(text = name.value, modifier = Modifier.clickable { name.value = swapName(name.value) } ) } } 02 Deep into the foundation State<Boolean>(false) State<String>(“Android”) State<Boolean>(true) State<Boolean>(false)
  18. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list
  19. Composition is a process of executing composable functions

  20. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation
  21. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation …..
  22. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. C A R D
  23. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. Group(456) C A R D C O L U M N
  24. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. Group(456) “Compose” C A R D C O L U M N
  25. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. Group(456) “Compose” Group(789) C A R D C O L U M N B U T T O N
  26. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. Group(456) “Compose” Group(789) C A R D C O L U M N B U T T O N { ... }
  27. Composer & SlotTable @Composable fun Card() { Column { Text(

    text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 02 Deep into the foundation Group(123) ….. Group(456) “Compose” Group(789) “Click me” C A R D C O L U M N B U T T O N { ... }
  28. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list
  29. Applier is an interface for materializing UI tree

  30. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list UiApplier: Applier implementation WindowRecomposer: LifecycleAware recomposer
  31. Layout phase steps 02 Deep into the foundation Column LayoutNode

    Button LayoutNode Text LayoutNode Text LayoutNode Root Node 1. Add child nodes bottom-up to the root node 2. Measure the child nodes 3. Define the size of a parent node 4. Place the child nodes on screen C O N S T R A I N T S M E A S U R E D S I Z E
  32. 02 Deep into the foundation UI Compiler Runtime 1 Composition

    2 Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list UiApplier: Applier implementation WindowRecomposer: LifecycleAware recomposer
  33. Final result 02 Deep into the foundation

  34. What is recomposition?

  35. Recomposition is a re-execution of composable functions in response to

    state changes
  36. 03 What is recomposition UI Compiler Runtime 1 Composition 2

    Layout 3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list UiApplier: Applier implementation WindowRecomposer: LifecycleAware recomposer
  37. Recomposition in SlotTable: example 1 @Composable fun Card() { val

    counter = remember { mutableStateOf(0) } Column { Text(text = "Compose") Button(onClick = { counter.value++ }) { Text(text = "Click me $counter") } } } 03 What is recomposition Group(456) ….. Group(789) “Compose” Group(101) “Click me 0” C A R D C O L U M N B U T T O N { ... } Group(123) State(0) R E M E M B E R
  38. Recomposition in SlotTable: example 1 @Composable fun Card() { val

    counter = remember { mutableStateOf(0) } Column { Text(text = "Compose") Button(onClick = { counter.value++ }) { Text(text = "Click me $counter") } } } 03 What is recomposition Group(456) ….. Group(789) “Compose” Group(101) “Click me 0” C A R D C O L U M N B U T T O N { ... } Group(123) State(0) R E M E M B E R
  39. Recomposition in SlotTable: example 1 @Composable fun Card() { val

    counter = remember { mutableStateOf(0) } Column { Text(text = "Compose") Button(onClick = { counter.value++ }) { Text(text = "Click me $counter") } } } 03 What is recomposition Group(456) ….. Group(789) “Compose” Group(101) “Click me 1” C A R D C O L U M N B U T T O N { ... } Group(123) State(1) R E M E M B E R
  40. Recomposition in SlotTable: example 2 @Composable fun Card() { val

    counter = remember { mutableStateOf(0) } Column { Text(text = "Compose $counter") Button(onClick = { counter.value++ }) { Text(text = "Click me") } } } 03 What is recomposition Group(456) ….. Group(789) “Compose 0” Group(101) “Click me” C A R D C O L U M N B U T T O N { ... } Group(123) State(0) R E M E M B E R
  41. Recomposition in SlotTable: example 2 @Composable fun Card() { val

    counter = remember { mutableStateOf(0) } Column { Text(text = "Compose $counter") Button(onClick = { counter.value++ }) { Text(text = "Click me") } } } 03 What is recomposition Group(456) ….. Group(789) “Compose 1” Group(101) “Click me” C A R D C O L U M N B U T T O N { ... } Group(123) State(1) R E M E M B E R
  42. 03 What is recomposition Compose Phases 1 Composition Calls composable

    functions and creates a description of the UI 2 Layout Measures the layout elements and their children and places them on the screen 3 Drawing Draws the UI elements to the screen
  43. Smart recomposition

  44. Inputs are stable Smart recomposition is an intelligent way to

    skip the recomposition of composable functions when their
  45. Stable type contract rules: RULE 1 The result of calls

    for two instances to `equals()` function will always be the same for the same two instances RULE 2 When a public property of the type changes, Composition is notified RULE 3 All public properties of the type are also stable
  46. Stable property types: Primitive types: Int, Boolean, Long, Char, Float,

    etc String Annotated with @Stable
  47. What about custom data types? 04 Smart Recomposition

  48. Exceptions: 1. Enum 2. Enum Entry 3. Interface 4. Annotation

    5. Companion Object 6. Inline 7. Anonymous object 8. Expect element 9. Inner class 04 Smart Recomposition Inferring Class stability • visits each eligible class • annotates with @StabilityInferred • helps compiler to determine what to recompose
  49. 04 Smart Recomposition UI Compiler Runtime 1 Composition 2 Layout

    3 Drawing Composition: 1. SlotTable 2. Composer 3. Changes List Applier: UI materializer Recomposer: 1. State Snapshot System 2. Compositions - list UiApplier: Applier implementation WindowRecomposer: LifecycleAware recomposer
  50. Tools to detect & debug recomposition

  51. 1. Recompose Highlighter ➢ Google Play team introduced the modifier

    ➢ Check out the code snippet below. 05 Tools to detect
  52. 2. Log Statements 05 Tools to detect ➢ Ancient and

    the most reliable. ➢ Android Studio Electric Eel has an automatic support for it. ➢ Wrapped as a modifier function. Check out the code snippet below
  53. 3. Compose Compiler Metrics 05 Tools to detect { "skippableComposables"

    : 64, "restartableComposables" : 76, "readonlyComposables" : 0, "totalComposables" : 76 } ➢ Full analysis ➢ Restartable & !skippable = ⛳ ➢ Check out the code snippet below for integration details
  54. Practical tips to leverage smart recomposition

  55. 1. Break down composable functions /** * 👍 : only

    associated column with changes gets recomposed. */ @Composable fun GoodGreetingsTip1() { Column { Greeting("Android", 1, Gray200) Greeting("Compose", 2, Brown200) } } @Composable fun Greeting(name: String, columnNumber: Int, background: Color) { Column { . . . } } 06 Practical tips
  56. 2. Use the key composable /** * 👍 : the

    key is set, and when item in the middle is removed, only LazyColumn gets recomposed * and not its children */ @Composable fun GoodGreetingsTip2(fruits: List<String>) { LazyColumn( modifier = Modifier .recompositionCounter("lazyColumn") ) { items( items = fruits, key = { it.name } ) { fruit -> Item(fruit) } } } 06 Practical tips
  57. 3. Read the state value at the lowest composable function

    /** * 👍 : only inner column and its children get recomposed, because the read happens * in one of the children. */ @Composable fun GoodGreetingsTip3(cardInfo: () -> CardInfo) { Column { UpperRow() GoodRow(cardInfo = cardInfo) } } @Composable private fun GoodRow(cardInfo: () -> CardInfo) { Row { GoodInnerColumn(cardInfo = cardInfo) } } 06 Practical tips
  58. 4. Use modifier lambdas /** * 👍: With the receiver

    lambda function (Modifier.offset{}) in place, this composable function doesn't get recomposed. */ @Composable private fun GoodGreetingsTip4(yAxis: () -> Int) { Row( modifier = Modifier .offset { IntOffset(y = yAxis(), x = 0) }, verticalAlignment = Alignment.Bottom ) { . . . } } 06 Practical tips
  59. 5. Utilize `derivedStateOf()` /** * 👍 : Only BottomBar gets

    recomposed whenever hideBottomBar's calculated state changes */ @Composable private fun GoodGreetingsTip5(list: List<FruitRow>) { val listState = rememberLazyListState() val hideBottomBar = remember { derivedStateOf { listState.isScrollInProgress } } Scaffold(bottomBar = { AnimatedVisibility(visible = !hideBottomBar.value) { BottomAppBar(content = { BottomNavigationItems ()}) } }, content = { LazyColumn(state = listState) { . . . } }) 06 Practical tips
  60. IMPORTANT 1 Move any logic out of the composable functions

    to viewmodels, presenters, etc 6. Tips worth re-repeating IMPORTANT 2 Don’t create dependable composable functions on the result of other composables functions IMPORTANT 3 Avoid any side-effects in the composable functions, because they can run in parallel
  61. Recap UI widgets are composable functions 3 Compose Phases UI

    widgets go through are Composition, Layout, Draw Recomposition is an expected process 3 Compose Layers Jetpack Compose uses to create composable functions: Compiler, Runtime, UI Practical tips should be used to avoid common recomposition pitfalls Smart recomposition should be leveraged as much as possible to teach Compiler
  62. Resources 1. Jetpack Compose Internals 2. Jetpack Compose source code

    3. Understanding Jetpack Compose — part 1 of 2 4. Understanding Jetpack Compose — part 2 of 2 5. Recomposition made easy 6. RecomposeHighlighter code snippet 7. Compose Compiler Metrics 8. A historical introduction to the Compose reactive state model 9. What is “donut-hole” skipping in Jetpack Compose? 10. Compose performance 11. Composable Metrics 12. Thinking in Compose 13. Jetpack Compose Phases 14. Jetpack Compose Lifecycle Resources Recomposition tips code samples
  63. THANK YOU www.cupsofcode.com Code | Live | Grow @aida_isay