Slide 1

Slide 1 text

What does Recomposition mean to your app? Aida Issayeva

Slide 2

Slide 2 text

About me 🤖 Android GDE 󰠁 Sr. Software Engineer 󰠅 Instructor 🐦 @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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

02 Deep into the foundation 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 02 Deep into the foundation Entrance to Jetpack Compose class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { ... setContent { Card() } } }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

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

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

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 } } } } ) 02 Deep into the foundation 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) } ) } } 02 Deep into the foundation State(false) State(“Android”) State(true) State(false)

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Composition is a process of executing composable functions

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") } } } 02 Deep into the foundation

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") } } } 02 Deep into the foundation …..

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") } } } 02 Deep into the foundation 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") } } } 02 Deep into the foundation 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") } } } 02 Deep into the foundation 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") } } } 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

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

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Applier is an interface for materializing UI tree

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Final result 02 Deep into the foundation

Slide 34

Slide 34 text

What is recomposition?

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

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

Slide 38

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

Slide 39

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

Slide 40

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

Slide 41

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Smart recomposition

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

What about custom data types? 04 Smart Recomposition

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Tools to detect & debug recomposition

Slide 51

Slide 51 text

1. Recompose Highlighter ➢ Google Play team introduced the modifier ➢ Check out the code snippet below. 05 Tools to detect

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Practical tips to leverage smart recomposition

Slide 55

Slide 55 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 { . . . } } 06 Practical tips

Slide 56

Slide 56 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) } } } 06 Practical tips

Slide 57

Slide 57 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) } } 06 Practical tips

Slide 58

Slide 58 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 ) { . . . } } 06 Practical tips

Slide 59

Slide 59 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) { . . . } }) 06 Practical tips

Slide 60

Slide 60 text

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

Slide 61

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

Slide 62 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 Resources Recomposition tips code samples

Slide 63

Slide 63 text

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