Slide 1

Slide 1 text

What does Recomposition mean to your app? Aida Issayeva Lite

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 Smart Recomposition 03 Tools to detect & debug 04 Practical tips 05 01 What is Recomposition? 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

01 Jetpack Compose Paradigm 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

Composition is a process of executing composable functions

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

What is recomposition?

Slide 10

Slide 10 text

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

Slide 11

Slide 11 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") } } } 02 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 12

Slide 12 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") } } } 02 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 13

Slide 13 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") } } } 02 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 14

Slide 14 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") } } } 02 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 15

Slide 15 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") } } } 02 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(0) R E M E M B E R

Slide 16

Slide 16 text

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

Slide 17 text

Smart recomposition

Slide 18

Slide 18 text

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

Slide 19

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

Slide 20 text

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

Slide 21

Slide 21 text

What about custom data types? 03 Smart Recomposition

Slide 22

Slide 22 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 03 Smart Recomposition Inferring Class stability ● visits each eligible class ● annotates with @StabilityInferred ● helps compiler to determine what to recompose

Slide 23

Slide 23 text

Tools to detect & debug recomposition

Slide 24

Slide 24 text

1. Recompose Highlighter ➢ Google Play team introduced the modifier ➢ Use this link below to check out the code snippet 04 Tools to detect

Slide 25

Slide 25 text

2. Log Statements 04 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 26

Slide 26 text

3. Compose Compiler Metrics 04 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 27

Slide 27 text

Practical tips to leverage smart recomposition

Slide 28

Slide 28 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 { . . . } } 05 Practical tips

Slide 29

Slide 29 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 } ) { fruit -> Item(fruit) } } } 05 Practical tips

Slide 30

Slide 30 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) } } 05 Practical tips

Slide 31

Slide 31 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 ) { . . . } } 05 Practical tips

Slide 32

Slide 32 text

IMPORTANT 1 Move any logic out of the composable functions to viewmodels, presenters, etc 5. 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 33

Slide 33 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 34

Slide 34 text

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