Slide 1

Slide 1 text

Mohit Sarveiya Guide to Improving Compose Performance @heyitsmohit Google Developer Expert in Kotlin & Android

Slide 2

Slide 2 text

Guide to Improving Compose Performance ● Recomposition performance

Slide 3

Slide 3 text

Guide to Improving Compose Performance ● Recomposition performance ● Compiler Plugin Internals

Slide 4

Slide 4 text

Guide to Improving Compose Performance ● Recomposition performance ● Compiler Plugin Internals ● Compiler Plugin Tests

Slide 5

Slide 5 text

Guide to Improving Compose Performance ● Recomposition performance ● Compiler Plugin Internals ● Compiler Plugin Tests ● Experimental features

Slide 6

Slide 6 text

Guide to Improving Compose Performance Performance Tip: Avoid premature optimizations

Slide 7

Slide 7 text

Guide to Improving Compose Performance Measure Improve Inspect

Slide 8

Slide 8 text

Performance Impacts ● Initial Composition time

Slide 9

Slide 9 text

Performance Impacts ● Initial Composition time ● Layout performance

Slide 10

Slide 10 text

Performance Impacts ● Initial Composition time ● Layout performance ● Recomposition performance

Slide 11

Slide 11 text

Composition Layout Draw Column Row Image Text Image

Slide 12

Slide 12 text

Composition Layout Draw Column Row Text Image Image

Slide 13

Slide 13 text

Composition Layout Draw Travel Row Column 100 pictures 10 videos

Slide 14

Slide 14 text

Guide to Improving Compose Performance Composition Layout Draw

Slide 15

Slide 15 text

Understanding Stability

Slide 16

Slide 16 text

Stability ● Allows us to skip execution of a composable body

Slide 17

Slide 17 text

Stability @Composable fun UsersScreen(modifier: Modifier, users: List) { Column { users.forEach { model -> User(model) } } }

Slide 18

Slide 18 text

Stability @Composable fun UsersScreen(modifier: Modifier, users: List) { Column { users.forEach { model -> User(model) } } }

Slide 19

Slide 19 text

Stability @Composable fun UsersScreen(modifier: Modifier, users: List) { Column { users.forEach { model -> UserRow(model) } } } @Composable fun UserRow(model: UserModel)

Slide 20

Slide 20 text

Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime )

Slide 21

Slide 21 text

Stability Frame 1 Frame 2 Frame 3

Slide 22

Slide 22 text

Stability Frame 1 Frame 2 Frame 3 Frame 4 Recompose UsersScreen

Slide 23

Slide 23 text

Stability Frame 1 Frame 2 Frame 3 Frame 4 Recompose UsersScreen Recomposition count increase

Slide 24

Slide 24 text

What makes a type stable? ● Immutability

Slide 25

Slide 25 text

What makes a type stable? ● Immutability ● Observable mutability (e.g. MutableState)

Slide 26

Slide 26 text

Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Mutable

Slide 27

Slide 27 text

Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) List

Slide 28

Slide 28 text

Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Unstable

Slide 29

Slide 29 text

Stability Under the Hood ● What are the implications of using unstable types?

Slide 30

Slide 30 text

Stability Under the Hood @Composable fun UsersScreen(…) { … } @Composable fun UserRow() { … } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Compose Compiler Plugin Transformed IR

Slide 31

Slide 31 text

Stability Under the Hood @Composable fun UsersScreen(modifier: Modifier, users: List) { Column { users.forEach { model -> UserRow(model) } } } @Composable fun UserRow(model: UserModel)

Slide 32

Slide 32 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { }

Slide 33

Slide 33 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { } %composer = %composer.startRestartGroup()

Slide 34

Slide 34 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { } if (%changed) { %dirty = %dirty or if (%composer.changedInstance(users)) } %composer = %composer.startRestartGroup()

Slide 35

Slide 35 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { … } … if (dirty and !%composer.skipping) { items.forEach { model: UserModel -> … } } %composer.endRestartGroup() ?. updateScope { %composer: Composer? -> UsersScreen(users, composer,…) } Recomposition scope

Slide 36

Slide 36 text

Unstable data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime )

Slide 37

Slide 37 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { } if (%changed and 0b0110 == 0) { %dirty = %dirty or if (%composer.changedInstance(users)) } if (dirty and !%composer.skipping) { … } %composer = %composer.startRestartGroup( <> ) Bit masking

Slide 38

Slide 38 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { } if (%changed and 0b0110 == 0) { %dirty = %dirty or if (%composer.changedInstance(users)) } if (dirty and !%composer.skipping) { … } %composer = %composer.startRestartGroup( <> ) enum class StabilityBits(val bits: Int) { UNSTABLE(0b100), STABLE(0b000); fun bitsForSlot(slot: Int): Int }

Slide 39

Slide 39 text

@Composable fun UsersScreen(users, %composer: Composer?, %changed: Int) { } if (%changed and 0b0110 == 0) { %dirty = %dirty or if (%composer.changedInstance(users)) } if (dirty and !%composer.skipping) { … } %composer = %composer.startRestartGroup( <> ) Instance Equality for unstable types

Slide 40

Slide 40 text

Stable Param class Model(val value: Int = 0) @Composable fun Test(state: Model) { A(state) }

Slide 41

Slide 41 text

Stable Param class Model(val value: Int = 0) @Composable fun example(state: Model) { A(state) }

Slide 42

Slide 42 text

Stable Param @Composable fun example(state, %composer) { %composer = %composer.startRestartGroup() … if (%changed) { %dirty = %dirty or if (%composer.changed(state)) 0b0100 else 0b0010 } … %composer.endRestartGroup() }

Slide 43

Slide 43 text

Stable Param @Composable fun example(state, %composer) { %composer = %composer.startRestartGroup( <> ) … if (%changed) { %dirty = %dirty or if (%composer.changed(state)) 0b0100 else 0b0010 } … %composer.endRestartGroup() } Equality check

Slide 44

Slide 44 text

Unskippable Conditions ● Stable parameters have differences from last execution

Slide 45

Slide 45 text

Unskippable Conditions ● Stable parameters have differences from last execution ● If the composer.skipping call returns false

Slide 46

Slide 46 text

Unskippable Conditions ● Stable parameters have differences from last execution ● If the composer.skipping call returns false ● Provided parameters to the function were unstable

Slide 47

Slide 47 text

Unskippable Composable @Composable fun UsersScreen(users: List) restartable fun UsersScreen( unstable users: List )

Slide 48

Slide 48 text

How to Analyze ● Stable & Unstable types ● Restartable and Skippable functions

Slide 49

Slide 49 text

Compiler Report @Composable fun UsersScreen(…) { … } @Composable fun UserRow() { … } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Compose Compiler Plugin Metrics

Slide 50

Slide 50 text

Compiler Report Module Metrics Class Stability Transformer Composer Lamba Memoization Composable Function Body Transformer Composable Param Transformer

Slide 51

Slide 51 text

Compiler Report interface ModuleMetrics { fun recordFunction( ... ) fun recordClass( ... ) fun recordLambda( ... ) fun saveMetricsTo( ... ) }

Slide 52

Slide 52 text

Compiler Report class ModuleMetricsImpl(var name: String) : ModuleMetrics { var skippableComposables = 0 var restartableComposables = 0 var readonlyComposables = 0 var markedStableClasses = 0 var inferredStableClasses = 0 var inferredUnstableClasses = 0 ... }

Slide 53

Slide 53 text

Compiler Report Inferred Stable Classes Marked Stable Classes Inferred Unstable Classes 70 0 Total Classes 61 9 Memoized Lambdas 76 Inferred Uncertain Classes 15

Slide 54

Slide 54 text

Compiler Report kotlinOptions { freeCompilerArgs += [ "-P", “plugin:…kotlin:reportsDestination=“ + buildDir.absolutePath + "/compose_metrics" ] }

Slide 55

Slide 55 text

Compiler Report (CI) Release Build Compiler Report Compare with Previous Stats

Slide 56

Slide 56 text

Compiler Report @Composable restartable fun UsersScreen(…, unstable users: List) @Composable fun UsersScreen(modifier: Modifier, users: List)

Slide 57

Slide 57 text

Stability Under the Hood ● How is stability determined?

Slide 58

Slide 58 text

Stability Under the Hood @Composable fun Profile(modifier: Modifier, user: UserModel) { ... } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime )

Slide 59

Slide 59 text

Stability Under the Hood data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) @StabilityInferred(parameters = 0) unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } Class Stability Transformer

Slide 60

Slide 60 text

Stability Under the Hood @StabilityInferred(parameters = 0) unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Synthesized Annotation

Slide 61

Slide 61 text

Stability Under the Hood Class Stability Transformer Stability Inferencer

Slide 62

Slide 62 text

Stability Under the Hood Stability Class Stability Transformer Stability Inferencer

Slide 63

Slide 63 text

Stability Under the Hood sealed class Stability { class Certain(…) : Stability() class Parameter(…) : Stability() class Combined(…) : Stability() class Runtime(…) : Stability() class Unknown(…) : Stability() … } Internal to Compiler

Slide 64

Slide 64 text

Stability Under the Hood sealed class Stability { class Certain(…) : Stability() class Parameter(…) : Stability() class Combined(…) : Stability() class Runtime(…) : Stability() class Unknown(…) : Stability() … }

Slide 65

Slide 65 text

Stability Under the Hood sealed class Stability { class Certain(…) : Stability() class Parameter(…) : Stability() class Combined(…) : Stability() class Runtime(…) : Stability() class Unknown(…) : Stability() … }

Slide 66

Slide 66 text

Stability Under the Hood unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } Stability.Certain(false)

Slide 67

Slide 67 text

Stability Under the Hood unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } Stability.Runtime

Slide 68

Slide 68 text

Compiler Report Inferred Stable Classes Marked Stable Classes Inferred Unstable Classes 70 0 Total Classes 61 9 Memoized Lambdas 76 Inferred Uncertain Classes 15

Slide 69

Slide 69 text

Stability Under the Hood class StabilityInferencer( ... ) { fun stabilityOf( declaration: IrClass ): Stability { ... } } What are the rules for stability?

Slide 70

Slide 70 text

Stability Under the Hood fun stabilityOf( declaration: IrClass ): Stability { val fqName = declaration.fqNameWhenAvailable if (KnownStableConstructs.stableTypes.contains(fqName)) { stability = Stability.Stable } }

Slide 71

Slide 71 text

Stability Under the Hood fun stabilityOf( declaration: IrClass ): Stability { val fqName = declaration.fqNameWhenAvailable if (KnownStableConstructs.stableTypes.contains(fqName)) { stability = Stability.Stable } } Known Stable Types

Slide 72

Slide 72 text

Known Stable Types ● Primitive types ● Pair, Triple ● Comparator, ClosedRange, ● Result

Slide 73

Slide 73 text

Stability Under the Hood class Foo(val a: List)

Slide 74

Slide 74 text

Stability Under the Hood // Stability.Certain(false) class Foo(val a: List)

Slide 75

Slide 75 text

Known Stable Types ● Guava Collections ● Kotlinx Immutable Collections ● Dagger Lazy

Slide 76

Slide 76 text

Stability Under the Hood message User name: String location: String lastLoggedIn: LocalDataTime } @Composable fun Profile(modifier: Modifier, user: User) { ... } Protobuff

Slide 77

Slide 77 text

Stability Under the Hood @Composable fun Profile(modifier: Modifier, user: User) { ... } message User name: String location: String lastLoggedIn: LocalDataTime } import com.google.protobuf.GeneratedMessageLite

Slide 78

Slide 78 text

Stability Under the Hood @Composable restartable fun Profile(modifier: Modifier, unstable user: User) { ... } message User name: String location: String lastLoggedIn: LocalDataTime } import com.google.protobuf.GeneratedMessageLite

Slide 79

Slide 79 text

https: / / / androidx/platform/frameworks/support/ Compose

Slide 80

Slide 80 text

Stability Under the Hood fun IrClass.isProtobufType(): Boolean { val directParentClassName = superTypes.lastOrNull { … }.fqNameWhenAvailable return directParentClassName == "com.google.protobuf.GeneratedMessageLite" || directParentClassName == "com.google.protobuf.GeneratedMessage" }

Slide 81

Slide 81 text

Stability Under the Hood fun IrClass.isProtobufType(): Boolean { val directParentClassName = superTypes.lastOrNull { … }.fqNameWhenAvailable return directParentClassName == "com.google.protobuf.GeneratedMessageLite" || directParentClassName == "com.google.protobuf.GeneratedMessage" }

Slide 82

Slide 82 text

Stability Under the Hood interface Foo { val value: Int }

Slide 83

Slide 83 text

Stability Under the Hood fun stabilityOf( declaration: IrClass ): Stability { }

Slide 84

Slide 84 text

Stability Under the Hood fun stabilityOf( declaration: IrClass ): Stability { ... if (declaration.isInterface) { return Stability.Unknown(declaration) } }

Slide 85

Slide 85 text

Stability Under the Hood class A class B class Foo(val pair: Pair)

Slide 86

Slide 86 text

Stability Under the Hood // Stability.Runtime class A class B class Foo(val pair: Pair)

Slide 87

Slide 87 text

Stable Markers Performance Tip: Use @Stable or @Immutable markers

Slide 88

Slide 88 text

Stable Markers unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime )

Slide 89

Slide 89 text

Stability Markers fun stabilityOf( declaration: IrClass ): Stability { if (declaration.hasStableMarkedDescendant()) { return Stability.Stable } … }

Slide 90

Slide 90 text

Stability Markers fun stabilityOf( declaration: IrClass ): Stability { if (declaration.hasStableMarkedDescendant()) { return Stability.Stable } … } Stability Marker Check

Slide 91

Slide 91 text

Stability Markers ● @Stable ● @Immutability

Slide 92

Slide 92 text

Stability Markers @Stable data class UserModel( val name: String, val friends: ImmutableList, val lastLoggedIn: LocalDateTime )

Slide 93

Slide 93 text

Stability Markers @StabilityInferred(parameters = 3) stable class UserModel { stable name: String stable friends: ImmutableList stable lastLoggedIn: LocalDateTime }

Slide 94

Slide 94 text

Stability Markers @Composable restartable fun Profile(…, stable user: UserModel) @Composable fun Profile(modifier: Modifier, user: UserModel) { ... }

Slide 95

Slide 95 text

Stability Markers @Composable restartable skippable fun Profile(…, stable user: UserModel) @Composable fun Profile(modifier: Modifier, user: UserModel) { ... }

Slide 96

Slide 96 text

Stability Module A (Compose) Module B (Compose not enabled) Unstable

Slide 97

Slide 97 text

Stability Under the Hood ● How is stability tested?

Slide 98

Slide 98 text

Stability Under the Hood data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) @StabilityInferred(parameters = 0) unstable class User { unstable name: String unstable friends: List unstable lastLoggedIn: LocalDateTime } Stability Inferencer

Slide 99

Slide 99 text

Testing Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Compose Compiler Plugin @Composable fun Profile( ... ) IR

Slide 100

Slide 100 text

Testing Stability data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) @Composable fun Profile( ... ) K1Compiler Facade K2Compiler Facade

Slide 101

Slide 101 text

Testing Stability class K2CompilerFacade { ... fun compileToIr(source): IrModuleFragment ... }

Slide 102

Slide 102 text

Testing Stability class Foo( val x: kotlinx.collections.immutable.ImmutableList )

Slide 103

Slide 103 text

Testing Stability val irModule = compileToIr(source) val stabilityInferencer = StabilityInferencer(irModule.descriptor) val classStability = stabilityInferencer.stabilityOf(irClass) // Stability.Stable

Slide 104

Slide 104 text

Testing Stability val irModule = compileToIr(source) val stabilityInferencer = StabilityInferencer(irModule.descriptor) val classStability = stabilityInferencer.stabilityOf(irClass) // Stability.Stable

Slide 105

Slide 105 text

Testing Stability val irModule = compileToIr(source) val stabilityInferencer = StabilityInferencer(irModule.descriptor) val classStability = stabilityInferencer.stabilityOf(irClass) // Stability.Stable

Slide 106

Slide 106 text

Testing Stability @Test fun testSingleValPrimitiveIsStable() = assertStability( "class Foo(val value: Int)", "Stable" ) Utility converts to IR

Slide 107

Slide 107 text

Testing Stability @Test fun testSingleValPrimitiveIsStable() = assertStability( "class Foo(val value: Int)", "Stable" )

Slide 108

Slide 108 text

Strategies ● Use stable types

Slide 109

Slide 109 text

Strategies ● Use stable types ● Use @Stable or @Immutable markers

Slide 110

Slide 110 text

Strategies ● Use stable types ● Use @Stable or @Immutable markers ● Mark NonRestartableComposable for “root”

Slide 111

Slide 111 text

Guide to Compose Performance Performance Tip: Mark NonRestartableComposable for “root” Composables.

Slide 112

Slide 112 text

Non Restartable Composable @Composable fun example(model: Unstable) { remember(model) { 1 } }

Slide 113

Slide 113 text

Non Restartable Composable @Composable fun example(model: Unstable) { remember(model) { 1 } } restartable fun example(unstable model: Unstable)

Slide 114

Slide 114 text

Non Restartable Composable @Composable fun example(model: Unstable, %composer: Composer?, %changed: Int) { %composer.startRestartGroup() val tmp0_group = %composer.cache(%composer.changed(model)) { 1 } ... %composer.endRestartGroup().updateScope({…}) }

Slide 115

Slide 115 text

Non Restartable Composable @Composable fun example(model: Unstable, %composer: Composer?, %changed: Int) { %composer.startRestartGroup() val tmp0_group = %composer.cache(%composer.changed(model)) { 1 } ... %composer.endRestartGroup().updateScope({…}) }

Slide 116

Slide 116 text

Non Restartable Composable @Composable @NonRestartableComposable fun example(model: Unstable) { remember(model) { 1 } }

Slide 117

Slide 117 text

Non Restartable Composable @Composable @NonRestartableComposable fun example(model: Unstable) { remember(model) { 1 } } fun example(unstable model: Unstable) Not restartable nor skippable

Slide 118

Slide 118 text

Non Restartable Composable @Composable fun example(model: Unstable, %composer: Composer?, %changed: Int) { val tmp0_group = %composer.cache(%composer.changedInstance(model)) { 1 } ... } No restart group created

Slide 119

Slide 119 text

! Only apply NonRestartableComposable to Composable not reading state value.

Slide 120

Slide 120 text

Experimental Features

Slide 121

Slide 121 text

Strong Skipping ● Enabled in Compose 1.7 alpha

Slide 122

Slide 122 text

Strong Skipping tasks.withType() { compilerOptions.freeCompilerArgs.addAll( "-P", "…featureFlag=StrongSkipping" ) }

Slide 123

Slide 123 text

Without Strong Skipping @Composable fun Profile(modifier: Modifier, user: User) { ... } restartable fun Profile( stable modifier: Modifier? = @static Companion unstable user: User ) Only restartable

Slide 124

Slide 124 text

With Strong Skipping @Composable fun Profile(modifier: Modifier, user: User) { ... } restartable skippable fun Profile( stable modifier: Modifier? = @static Companion unstable user: User ) All restartable functions are skippable

Slide 125

Slide 125 text

Strong Skipping ● Unstable —> Instance Equality ● Stable —> Object Equality

Slide 126

Slide 126 text

@Composable fun Profile(user, %composer: Composer?, %changed: Int) { } if (%changed and 0b0110 == 0) { %dirty = %dirty or if (%composer.changedInstance(users)) } if (dirty and !%composer.skipping) { … } %composer = %composer.startRestartGroup( <> ) enum class StabilityBits(val bits: Int) { UNSTABLE(0b100), STABLE(0b000); fun bitsForSlot(slot: Int): Int }

Slide 127

Slide 127 text

Strong Skipping (Opt out) @Composable @NonSkippableComposable fun Profile(modifier: Modifier, user: User) { ... }

Slide 128

Slide 128 text

Strong Skipping Lambdas class Unstable(var value: Int = 0) @Composable fun example() { val unstableObject = Unstable(0) val lambda = { unstableObject } }

Slide 129

Slide 129 text

Strong Skipping Lambdas @Composable fun example(%composer: Composer?, %changed: Int) { … if (%changed) { val foo = Unstable(0) val lambda = %composer.cache( %composer.changedInstance(unstableObject)) { { unstableObject } } … } Memoize Lambda

Slide 130

Slide 130 text

Strong Skipping Lambdas (Opt out) class Unstable(var value: Int = 0) @Composable fun example() { val unstableObject = Unstable(0) val lambda = @DontMemoize { unstableObject } }

Slide 131

Slide 131 text

Strong Skipping Lambdas (Opt out) @Composable fun example(%composer: Composer?, %changed: Int) { ... if (%changed) { val unstableObject = Unstable(0) val lambda = { { unstableObject } } } ... } Not Memoized

Slide 132

Slide 132 text

Strong Skipping tasks.withType() { compilerOptions.freeCompilerArgs.addAll( "-P", "…featureFlag=StrongSkipping" ) }

Slide 133

Slide 133 text

● Undocumented feature flag Non Skipping Group Optimization

Slide 134

Slide 134 text

● Before turning on feature flag Non Skipping Group Optimization

Slide 135

Slide 135 text

Non Restartable Composable @Composable @NonRestartableComposable fun example(model: Unstable)

Slide 136

Slide 136 text

@Composable @NonRestartableComposable fun example(model, %composer: Composer?) { } %composer = %composer.startReplaceGroup() gets a replace group placed around the body

Slide 137

Slide 137 text

@Composable @NonRestartableComposable fun example(model, %composer: Composer?) { } %composer = %composer.startReplaceGroup() if (%changed) { %dirty = %dirty or if (%composer.changedInstance(…)) } Never calls `$composer.changed( ... )` with its parameters

Slide 138

Slide 138 text

@Composable @NonRestartableComposable fun example(model, %composer: Composer?) { } %composer = %composer.startReplaceGroup() if (show) { %composer.startMoveableGroup() … %composer.endMovableGroup() } Proper groups around control flow

Slide 139

Slide 139 text

● After turning on feature flag Non Skipping Group Optimization

Slide 140

Slide 140 text

@Composable @NonRestartableComposable fun example(model, %composer: Composer?) { } %composer = %composer.startReplaceGroup() if (show) { %composer.startMoveableGroup() … %composer.endMovableGroup() } Not necessary

Slide 141

Slide 141 text

@Composable @NonRestartableComposable fun example(model, %composer: Composer?) { … } Never omit if (show) { %composer.startMoveableGroup() … %composer = %composer.endMovableGroup() }

Slide 142

Slide 142 text

● Strong skipping ● Non Skipping Group Optimization Experimental features

Slide 143

Slide 143 text

Guide to Improving Compose Performance Performance Tip: Reduce recompositions by reusing moveContentOf

Slide 144

Slide 144 text

moveableContentOf val content = remember { content } if (showVertical) { Column { content() } } else { Row { content() } }

Slide 145

Slide 145 text

moveableContentOf val content = remember { content } if (portrait) { Column { content() } } else { Row { content() } }

Slide 146

Slide 146 text

moveableContentOf val content = remember { moveableContentOf(content) } if (showVertical) { Column { content() } } else { Row { content() } }

Slide 147

Slide 147 text

moveableContentOf ● Nodes are preserved.

Slide 148

Slide 148 text

moveableContentOf Column Content Text Image

Slide 149

Slide 149 text

moveableContentOf Row Content Text Image

Slide 150

Slide 150 text

moveableContentOf ● Moves all remembered state.

Slide 151

Slide 151 text

Inside Compiler Plugin Compose Compiler Plugin Test Recompositions val content = remember { content } if (showVertical) { Column { content() } } else { Row { content() } }

Slide 152

Slide 152 text

Inside Compiler Plugin interface CompositionTestScope : CoroutineScope { fun compose(block: @Composable () -> Unit) fun revalidate() ... }

Slide 153

Slide 153 text

Inside Compiler Plugin val content = movableContentOf { val privateState = remember { mutableStateOf(0) } Text("Heading") Text("Content") }

Slide 154

Slide 154 text

Inside Compiler Plugin var portrait by mutableStateOf(false) val content = movableContentOf { val privateState = remember { mutableStateOf(0) } Text("Heading") Text(“Content") }

Slide 155

Slide 155 text

Inside Compiler Plugin @Composable fun example() { if (portrait) { Column { content() } } else { Row { content() } } }

Slide 156

Slide 156 text

Inside Compiler Plugin var lastPrivateState: State = mutableStateOf(0) var portrait by mutableStateOf(false) val content = movableContentOf { val privateState = remember { mutableStateOf(0) } lastPrivateState = privateState Text("Heading") Text(“Content") }

Slide 157

Slide 157 text

Inside Compiler Plugin interface CompositionTestScope : CoroutineScope { ... fun compose(block: @Composable () -> Unit) fun revalidate() ... }

Slide 158

Slide 158 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState Content() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 159

Slide 159 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState example() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 160

Slide 160 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState example() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 161

Slide 161 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState example() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 162

Slide 162 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState example() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 163

Slide 163 text

Inside Compiler Plugin compose { val firstPrivateState = lastPrivateState example() portrait = true Snapshot.sendApplyNotifications() revalidate() assertSame(firstPrivateState, lastPrivateState) }

Slide 164

Slide 164 text

Inside Compiler Plugin var portrait by mutableStateOf(false) val content = movableContentOf { val privateState = remember { mutableStateOf(0) } lastPrivateState = privateState Text("Heading") Text(“Content") }

Slide 165

Slide 165 text

moveableContentOf ● Nodes are preserved. ● State is remembered as groups are moved.

Slide 166

Slide 166 text

Guide to Improving Compose Performance Performance Tip: Use derivedStateOf for frequently changing state

Slide 167

Slide 167 text

derivedStateOf @Composable fun Profile() { var shouldShowFab = remember(lazyListState.firstVisibleItemIndex) { lazyListState.firstVisibleItemIndex > 10 } if(shouldShowFab){ /* Display the floating action button */ } }

Slide 168

Slide 168 text

derivedStateOf @Composable fun Profile() { var shouldShowFab = remember(lazyListState.firstVisibleItemIndex) { lazyListState.firstVisibleItemIndex > 10 } if(shouldShowFab){ /* Display the floating action button */ } }

Slide 169

Slide 169 text

derivedStateOf @Composable fun Profile() { var shouldShowFab = remember(lazyListState.firstVisibleItemIndex) { lazyListState.firstVisibleItemIndex > 10 } if(shouldShowFab){ /* Display the floating action button */ } }

Slide 170

Slide 170 text

derivedStateOf Frame 1 Frame 2 Frame 3

Slide 171

Slide 171 text

derivedStateOf Profile Frame 1 Frame 2 Frame 3 Frame 4 Recompose Recomposition count increase Pro .. Pro. Pro. Pro. Pro. Pro. Pro. Pro. Pro.

Slide 172

Slide 172 text

derivedStateOf @Composable fun Profile() { var shouldShowFab = remember(lazyListState.firstVisibleItemIndex) { lazyListState.firstVisibleItemIndex > 10 } if(shouldShowFab){ /* Display the floating action button */ } } Value changes very frequently

Slide 173

Slide 173 text

derivedStateOf @Composable fun Profile() { val shouldShowFab = remember(lazyListState.firstVisibleItemIndex){ derivedStateOf { lazyListState.firstVisibleItemIndex > 10 } } … }

Slide 174

Slide 174 text

derivedStateOf fun derivedStateOf( policy: SnapshotMutationPolicy, calculation: () -> T, ): State

Slide 175

Slide 175 text

derivedStateOf fun derivedStateOf( policy: SnapshotMutationPolicy, calculation: () -> T, ): State How do we compare previous values?

Slide 176

Slide 176 text

Snapshot Mutation Policy ● Referential Equality Policy ● Structural Equality Policy

Slide 177

Slide 177 text

Inside Compiler Plugin @Composable fun UsersScreen(…) { … } @Composable fun UserRow() { … } data class UserModel( var name: String, val friends: List, val lastLoggedIn: LocalDateTime ) Compose Compiler Plugin Test Snapshot changes

Slide 178

Slide 178 text

Inside Compiler Plugin class SnapshotStateObserver(…) { fun observeReads(…) fun start() fun stop() fun notifyChanges() … }

Slide 179

Slide 179 text

Inside Compiler Plugin class SnapshotStateObserver(…) { fun observeReads(…) fun start() fun stop() fun notifyChanges() … }

Slide 180

Slide 180 text

Inside Compiler Plugin fun runSimpleTest( block: (…) -> Unit ) { val stateObserver = SnapshotStateObserver { it() } try { stateObserver.start() block(stateObserver,…) Snapshot.sendApplyNotifications() } finally { stateObserver.stop() } }

Slide 181

Slide 181 text

Inside Compiler Plugin fun runSimpleTest( block: (…) -> Unit ) { val stateObserver = SnapshotStateObserver { it() } try { stateObserver.start() block(stateObserver,…) Snapshot.sendApplyNotifications() } finally { stateObserver.stop() } }

Slide 182

Slide 182 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) val derivedState = derivedStateOf(referentialEqualityPolicy()) { state.value } }

Slide 183

Slide 183 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) val derivedState = derivedStateOf(referentialEqualityPolicy()) { state.value } }

Slide 184

Slide 184 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) val derivedState = derivedStateOf(referentialEqualityPolicy()) { state.value } }

Slide 185

Slide 185 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> … var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } }

Slide 186

Slide 186 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } state.value = mutableListOf(1) } Update State

Slide 187

Slide 187 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) val derivedState = derivedStateOf(referentialEqualityPolicy()) { state.value } state.value = mutableListOf(1) } State will observe read due to ref check

Slide 188

Slide 188 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } state.value = mutableListOf(1) assertEquals(1, changes) } Changes is updated

Slide 189

Slide 189 text

Referential Equality Policy runSimpleTest { stateObserver, _ -> var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } state.value = mutableListOf(1) assertEquals(1, changes) }

Slide 190

Slide 190 text

Snapshot Mutation Policy ● Referential Equality Policy ● Structural Equality Policy

Slide 191

Slide 191 text

Structural Equality Policy runSimpleTest { stateObserver, _ -> val state = mutableStateOf(mutableListOf(1)) val derivedState = derivedStateOf(structureEqualityPolicy()) { state.value } }

Slide 192

Slide 192 text

Structural Equality Policy runSimpleTest { stateObserver, _ -> var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } state.value = mutableListOf(1) assertEquals(0, changes) // changes = 0 } Value is not emitted by derived state

Slide 193

Slide 193 text

Structural Equality Policy runSimpleTest { stateObserver, _ -> var changes = 0 stateObserver.observeReads(…, { changes ++ }) { derivedState.value } state.value = mutableListOf(1) assertEquals(0, changes) // changes = 0 }

Slide 194

Slide 194 text

Guide to Improving Compose Performance Performance Tip: Avoid premature optimizations

Slide 195

Slide 195 text

Guide to Improving Compose Performance Measure Improve Inspect

Slide 196

Slide 196 text

Guide to Improving Compose Performance ● Recomposition performance ● Compiler Plugin Internals ● Compiler Plugin Tests ● Experimental features

Slide 197

Slide 197 text

Thank You! www.codingwithmohit.com @heyitsmohit