Slide 1

Slide 1 text

Compose Compiler & Runtime @CanyuDev • Can Yumusak

Slide 2

Slide 2 text

What is a @Composable? • @Composable functions are DSLs • Composition: @Composable → Composition Tree • @Composable updates its node on recomposition @CanyuDev

Slide 3

Slide 3 text

@Composable Superpowers @CanyuDev

Slide 4

Slide 4 text

🥸 Context aware • Only callable from other @Composable • @Composable ≡ suspend @CanyuDev

Slide 5

Slide 5 text

🔄 Restartable Code-Blocks Application() TopAppBar() Counter() Text() Button() @CanyuDev

Slide 6

Slide 6 text

⏭ Skippable Code-Blocks Application() TopAppBar() Counter() Text() Button() @CanyuDev

Slide 7

Slide 7 text

🧠 Memoization • Values can be retained between compositions • Skip if unchanged input ⏭ val result = remember(input) { anExpensiveComputation(input) } @CanyuDev

Slide 8

Slide 8 text

Kotlin Code Journey Kotlin Compiler *.kt *.kt *.kt *.kt *.kt *.class ▶ 
 Running App Compose UI Compose Runtime @CanyuDev

Slide 9

Slide 9 text

Compose Compiler Plugin @CanyuDev

Slide 10

Slide 10 text

💡 Kotlin Compiler Plugin • Hook into Compiler, modify ByteCode • Popular Example • kotlinx.serialization @CanyuDev

Slide 11

Slide 11 text

🔎 Code Generation • Disclaimer: Everything is simplified~ @Composable fun Counter() { var count by remember { mutableStateOf(0) } Text( text = "Count: $count", modifier = Modifier.clickable { count ++ }, ) } @CanyuDev

Slide 12

Slide 12 text

🔎 Code Generation fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) if (changed = = 0 & & composer.getSkipping()) { composer.skipToGroupEnd() } else { var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count”, Modifier.clickable { count + + }, ) } composer.endRestartGroup() ? . let { it.updateScope { composer, force -> Counter(composer, changed or 1) } } } @CanyuDev

Slide 13

Slide 13 text

🔎 Code Generation: Passing Context fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) if (changed = = 0 & & composer.getSkipping()) { composer.skipToGroupEnd() } else { var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count”, Modifier.clickable { count + + }, ) } composer.endRestartGroup() ? . let { it.updateScope { composer, force -> Counter(composer, changed or 1) } } } @CanyuDev

Slide 14

Slide 14 text

🔎 Code Generation: Skipping fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) if (changed = = 0 & & composer.getSkipping()) { composer.skipToGroupEnd() } else { var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count”, Modifier.clickable { count + + }, ) } composer.endRestartGroup() ? . let { it.updateScope { composer, force -> Counter(composer, changed or 1) } } } @CanyuDev

Slide 15

Slide 15 text

🔎 Code Generation: Restarting fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) if (changed = = 0 & & composer.getSkipping()) { composer.skipToGroupEnd() } else { var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count”, Modifier.clickable { count + + }, ) } composer.endRestartGroup() ? . let { it.updateScope { composer, force -> Counter(composer, changed or 1) } } } @CanyuDev

Slide 16

Slide 16 text

🔎 Code Generation: Restarting fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) if (changed = = 0 & & composer.getSkipping()) { composer.skipToGroupEnd() } else { var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count”, Modifier.clickable { count + + }, ) } composer.endRestartGroup() ? . let { it.updateScope { composer, force -> Counter(composer, changed or 1) } } } @CanyuDev

Slide 17

Slide 17 text

Composition Tree / Slot Table @CanyuDev

Slide 18

Slide 18 text

@CanyuDev

Slide 19

Slide 19 text

@CanyuDev

Slide 20

Slide 20 text

Example fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 21

Slide 21 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 22

Slide 22 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 23

Slide 23 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 24

Slide 24 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 25

Slide 25 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 26

Slide 26 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 27

Slide 27 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 28

Slide 28 text

Example: Composition fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev

Slide 29

Slide 29 text

Example: Composition Result @CanyuDev

Slide 30

Slide 30 text

Example: Recompostion fun Counter(composer: Composer, changed: Int) { composer.startRestartGroup(001) var count by remember(composer, 0) { mutableStateOf(0) } Text( composer, 0, "Count: $count", Modifier.clickable { count + + }, ) composer.endRestartGroup() } @CanyuDev ↩

Slide 31

Slide 31 text

Recomposition • “State” is observed by runtime 🔍 • “State” read attributed “UpdateScope” • Assigned “UpdateScope” is invoked @CanyuDev

Slide 32

Slide 32 text

“Remember” Operator @Composable fun Application() { val state = remember() { "test" } Text(state) } @CanyuDev

Slide 33

Slide 33 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test" // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev

Slide 34

Slide 34 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test" // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev

Slide 35

Slide 35 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test" // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev

Slide 36

Slide 36 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test” // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev

Slide 37

Slide 37 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test" // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev ↩

Slide 38

Slide 38 text

“Remember” Operator fun Application(…) { val remembered = composer.rememberedValue() val value: String if (remembered != Composer.Companion.Empty) { value = remembered as String } else { val newValue = “test" // { "test" } composer.updateRememberedValue(newValue) value = newValue } Text(value) } @CanyuDev ↩

Slide 39

Slide 39 text

💥 What about if/else? • Group ID mismatch detection • Solved by deleting group in slot table • Expensive but Rare @CanyuDev

Slide 40

Slide 40 text

🔑 Key Takeaways • Compiler Plugin generates Code • Generated code reads / writes <> Slot Table • Skip known code parts • Only emit new changes @CanyuDev

Slide 41

Slide 41 text

🧐 Summary • Compose Runtime is a very powerful diffing tool • Many other applications! @CanyuDev Jetpack Compose internals Jorge Castillo

Slide 42

Slide 42 text

Questions? @CanyuDev 👋 That’s it folks Can Yumusak