Slide 1

Slide 1 text

What does Recomposition mean to your app? Aida Issayeva August 2, 2022 Chicago Roboto @aida_isay Android GDE

Slide 2

Slide 2 text

About me 🤖 Android GDE 󰠁 Sr. Software Engineer 󰠅 WomenTechmakers' ambassador 🐦 @aida_isay 🌐 cupsofcode.com 📽 @aida_isay

Slide 3

Slide 3 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 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Deep into the foundation 02

Slide 7

Slide 7 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 8

Slide 8 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

Slide 9

Slide 9 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

Slide 10

Slide 10 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 11

Slide 11 text

Compose Layers UI Compiler Runtime 1 Composition 2 Layout 3 Drawing

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

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 14

Slide 14 text

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

Slide 15

Slide 15 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 16

Slide 16 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 17

Slide 17 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 18

Slide 18 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 19

Slide 19 text

Composition is a process of executing composable functions

Slide 20

Slide 20 text

Composition Composer SlotTable Change List Manager Update State

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

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) ….. C A R D

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) 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” C A R D C O L U M 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) C A R D C O L U M N B U T T O N { ... }

Slide 28

Slide 28 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 29

Slide 29 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 30

Slide 30 text

Applier is an interface for materializing UI tree

Slide 31

Slide 31 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 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

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

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

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 36

Slide 36 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 37

Slide 37 text

Final result

Slide 38

Slide 38 text

What is recomposition? 03

Slide 39

Slide 39 text

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

Slide 40

Slide 40 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 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 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 43

Slide 43 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 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 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 45

Slide 45 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 46

Slide 46 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 47

Slide 47 text

Smart recomposition 04

Slide 48

Slide 48 text

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

Slide 49

Slide 49 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 50

Slide 50 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 51

Slide 51 text

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

Slide 52

Slide 52 text

What about custom data types?

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Tools to detect & debug recomposition 05

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Practical tips to leverage smart recomposition 06

Slide 61

Slide 61 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 62

Slide 62 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 63

Slide 63 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 64

Slide 64 text

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

Slide 65

Slide 65 text

5. Utilize `derivedStateOf()` /** * 👍 : Only BottomBar gets recomposed whenever hideBottomBar's calculated state changes */ @Composable private 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 66

Slide 66 text

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

Slide 67

Slide 67 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 68

Slide 68 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. Compose Tooling Recomposition tips code samples

Slide 69

Slide 69 text

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