Slide 1

Slide 1 text

What does Recomposition mean to your app? Aida Issayeva Senior Software Engineer Android GDE @aida_isay

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Jetpack Compose paradigm UI widgets are functions They are stateless Different arguments, passed to the functions, trigger updates

Slide 4

Slide 4 text

SlotTable Android OS Composer Applier UI Node Tree State Snapshot System Change List Applier

Slide 5

Slide 5 text

Deep into the foundation 02

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

Slide 8

Slide 8 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

Slide 9

Slide 9 text

From Android OS: ● Context ● Configuration ● Lifecycle ● Keyboard state ● SavedStateInstance ● etc CompositionLocals Entrance to Jetpack Compose class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... setContent { Card() } } }

Slide 10

Slide 10 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

scheduler for performing recomposition and applying updates to one or more Compositions. Recomposer

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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 } } } } ) Android OS State Snapshot System

Slide 16

Slide 16 text

State(“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) } ) } } State(false) State(“Android”) State(true) State(false)

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Composition process of executing composable functions

Slide 19

Slide 19 text

Composition Composer SlotTable Change List Manager Update State

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Composer & SlotTable @Composable fun Card() { Column { Text( text = "Compose", fontSize = 24.sp, fontWeight = FontWeight.Bold ) Button(onClick = { }) { Text(text = "Click me") } } } 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 { ... }

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Applier interface for materializing UI tree

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Layout phase steps Column LayoutNode Button LayoutNode Text LayoutNode Text LayoutNode Root Node 1. Add child nodes bottom-up to the root node

Slide 32

Slide 32 text

Layout phase steps 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 C O N S T R A I N T S

Slide 33

Slide 33 text

Layout phase steps 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 C O N S T R A I N T S M E A S U R E D S I Z E

Slide 34

Slide 34 text

Layout phase steps 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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Final result

Slide 37

Slide 37 text

What is recomposition? 03

Slide 38

Slide 38 text

Recomposition re-execution of composable functions in response to state changes

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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") } } } 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

Slide 41

Slide 41 text

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") } } } 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

Slide 42

Slide 42 text

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") } } } 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

Slide 43

Slide 43 text

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") } } } 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

Slide 44

Slide 44 text

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") } } } 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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Smart recomposition 04

Slide 47

Slide 47 text

Inputs are stable Smart recomposition is an intelligent way to skip the recomposition of composable functions when their

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Stable type contract rules The result of `equals()` function is the same All public properties are stable Public property is changed - composition is notified Rule 1 Rule 3 Rule 2

Slide 50

Slide 50 text

Stable property types Primitive types: Int, Boolean, Long, Char, Float, etc String Annotated with @Stable

Slide 51

Slide 51 text

What about custom data types?

Slide 52

Slide 52 text

Inferring Class stability 1 Visit each eligible class 2 Annotate with @StabilityInferred 3 Help Compiler to determine

Slide 53

Slide 53 text

Exceptions Enum Enum Entry Interface Annotation Companion Object Inline Anonymous Object Expect Element Inner class

Slide 54

Slide 54 text

Tools to detect & debug recomposition 05

Slide 55

Slide 55 text

1. Compose Compiler Metrics { "skippableComposables" : 64, "restartableComposables" : 76, "readonlyComposables" : 0, "totalComposables" : 76 } Full analysis ✅ Release build ❗ Skippable fun ✅

Slide 56

Slide 56 text

2. Recompose Highlighter Visual appeal ✅ Nested functions ❗ Labor-consuming ❗

Slide 57

Slide 57 text

3. Logging Modifier Too much noise ❗ Easy to add ✅ Labor-consuming ❗

Slide 58

Slide 58 text

№ of compositions ✅ № of skips ✅ No manual add ✅ Canary build ❗ JC v1.2.0-alpha03+ ❗ 4. Recomposition counter in Android Studio EE

Slide 59

Slide 59 text

Practical tips to leverage smart recomposition 06

Slide 60

Slide 60 text

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 { . . . } }

Slide 61

Slide 61 text

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) { LazyColumn( modifier = Modifier .recompositionCounter("lazyColumn") ) { items( items = fruits, key = { it.name } ) { fruit -> Item(fruit) } } }

Slide 62

Slide 62 text

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) } }

Slide 63

Slide 63 text

4. Use modifier lambdas /** * 👍: With the receiver lambda function (Modifier.offset{}) in place, this composable function doesn't get recomposed. */ @Composable fun GoodGreetingsTip4(yAxis: () -> Int) { Row( modifier = Modifier .offset { IntOffset(y = yAxis(), x = 0) }, verticalAlignment = Alignment.Bottom ) { . . . } }

Slide 64

Slide 64 text

5. Utilize `derivedStateOf()` /** * 👍 : Only BottomBar gets recomposed whenever hideBottomBar's calculated state changes */ @Composable fun GoodGreetingsTip5(list: List) { val listState = rememberLazyListState() val hideBottomBar = remember { derivedStateOf { listState.isScrollInProgress } } Scaffold(bottomBar = { AnimatedVisibility(visible = !hideBottomBar.value) { BottomAppBar(content = { BottomNavigationItems ()}) } }, content = { LazyColumn(state = listState) { . . . } })

Slide 65

Slide 65 text

6. Declare properties as read-only /** * 👍: `name` property inside Fruit is declared as read-only. When Checkbox is toggled, the recomposition of Text UI widget is skipped */ data class Fruit(val name: String) @Composable fun GoodGreetingsTip6(fruit: Fruit) { var isChecked by remember { mutableStateOf(false) } Column { Text(text = fruit.name) Checkbox(checked = isChecked, onCheckedChange = { isChecked = !isChecked }) } }

Slide 66

Slide 66 text

7. Leverage KotlinX immutable collections /** * 👍: When it comes to reg collections, Compiler can't determine their stability, because they're interfaces and underlying implementation can be mutable, therefore it marks them unstable.When using kotlinX immutable collections, it's guaranteed they'll be immutable, meaning they will be inferred by the Compiler. */ @Composable fun GoodGreetingsTip7() { fruits: ImmutableList = persistentListOf() Column { fruits.forEach { fruit -> Text(text = fruit.name) } } } in alpha

Slide 67

Slide 67 text

7. Tips worth repeating: Logic out of composables Execution in any order & parallel Side-effect free ViewModels Callbacks Optimization

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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 15. Jetpack Compose Stability Explained 16. Compose Tooling Recomposition tips code samples

Slide 70

Slide 70 text

THANK YOU www.cupsofcode.com Code | Live | Grow @aida_isay