Slide 1

Slide 1 text

Hitchhiking through Jetpack Compose Amanda Hinchman-Dominguez Kotlin GDE, Android Engineer Jossi Wolf Software Engineer @ Google #hg2compose

Slide 2

Slide 2 text

@mvndy_hd @jossiwolf #hg2compose

Slide 3

Slide 3 text

Why Compose? @mvndy_hd @jossiwolf #hg2compose

Slide 4

Slide 4 text

@mvndy_hd @jossiwolf #hg2compose @Composable fun MyApp() { var counter by remember { mutableStateOf(0) } HelloWorld(“Clicked $counter times”) Button(onClick = { counter.+ }) { … } } @Composable fun HelloWorld(greeting: String) { Text(“Hello World, $greeting”) }

Slide 5

Slide 5 text

Compose Phases @mvndy_hd @jossiwolf #hg2compose Composition Layout Drawing Compiler

Slide 6

Slide 6 text

"A Snapshot is a lot like a save point in a video game: it represents the state of your entire program at a single point in history." - "Introduction to the Compose Snapshot System" by Zach Klippenstein Snapshots 📸 @mvndy_hd @jossiwolf #hg2compose

Slide 7

Slide 7 text

1 Snapshots 📸 data class Dog( val name: MutableState ) Dog(name = "Spot") @mvndy_hd @jossiwolf #hg2compose

Slide 8

Slide 8 text

Snapshot.takeMutableSnapshot() 1 2 Snapshots 📸 Snapshot.takeSnapshot() 📸 Dog(name = "Spot") “Spot” @mvndy_hd @jossiwolf #hg2compose

Slide 9

Slide 9 text

1 2 Snapshots 📸 📸 Dog(name = "Spot") “Spot” dog.name.value = "Fido" Dog(name = "Fido") @mvndy_hd @jossiwolf #hg2compose

Slide 10

Slide 10 text

1 2 Snapshots 📸 📸 Dog(name = "Spot") “Spot” dog.name.value = "Fido" Dog(name = "Fido") @mvndy_hd @jossiwolf #hg2compose println(dog.name.value) // Fido

Slide 11

Slide 11 text

1 2 Snapshots 📸 📸 Dog(name = "Spot") “Spot” dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } @mvndy_hd @jossiwolf #hg2compose

Slide 12

Slide 12 text

1 2 Snapshots 📸 📸 Dog(name = "Spot") “Spot” dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } Dog(name = "Taco") @mvndy_hd @jossiwolf #hg2compose

Slide 13

Slide 13 text

1 2 Snapshots 📸 📸 Dog(name = "Spot") “Spot” dog.name.value = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } Dog(name = "Taco") snapshot.apply() “Taco” @mvndy_hd @jossiwolf #hg2compose

Slide 14

Slide 14 text

- State changes cause recomposition - Compose has a Snapshot system - Snapshots power state Recap! @mvndy_hd @jossiwolf #hg2compose

Slide 15

Slide 15 text

https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn fun main() { val dog = Dog() dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot( readObserver = { readObject .> } ) snapshot.enter { println(dog.name.value) } } Snapshots 📸 @mvndy_hd @jossiwolf #hg2compose

Slide 16

Slide 16 text

#hg2compose fun main() { val dog = Dog() dog.name.value = "Spot" val snapshot = Snapshot.takeSnapshot( readObserver = { readObject .> ./ MutableState(value = "Spot") } ) snapshot.enter { println(dog.name.value) } } Snapshots 📸 @mvndy_hd @jossiwolf https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn

Slide 17

Slide 17 text

Managing state in Compose @mvndy_hd @jossiwolf #hg2compose

Slide 18

Slide 18 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } #hg2compose @mvndy_hd @jossiwolf Generating Groups in Compose

Slide 19

Slide 19 text

@Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer = %composer.startRestartGroup(.>) val %dirty = %changed if (%changed and 0b1110 ..= 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 ..= 0 .| !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ..updateScope { %composer: Composer?, %force: Int .> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf

Slide 20

Slide 20 text

Restartable Moveable Replaceable @mvndy_hd @jossiwolf Types of Groups #hg2compose

Slide 21

Slide 21 text

Groups are generated by the Compose compiler plugin #hg2compose @mvndy_hd @jossiwolf

Slide 22

Slide 22 text

#hg2compose @mvndy_hd @jossiwolf The Kotlin Compiler .kt Lexer Analysis Syntax Analysis Generates tokens Build PSI tree Add info to nodes Analysis Resolution Phase Generate descriptors for PSI + Symbol Tables + + Enhance PSI elements w/ descriptors + Symbol table maps PSI to descriptor/IR calls Run static analysis, type verification, diagnostics Resolve + + Parsing Phase + + + + + + ● A compiler does two things with data: ○ Compiles - changes format ○ Lowers - simplifies data IR Transformations Optimizing Phase < > Bytecode Codegen Phase Analyzes IR data to create additional logic - Call graph - Control-flow graph CPU-related IR Transformations FUNC VAL_PARAM VAL_PARAM VA:_PARAM Unoptimized IR - Performs additional analysis, transformations + optimisations specific to target CPU architecture - Analyzes IR optimized data to create CPU-related logic i.e. multithreading IR-to-Bytecode Generation Perform optimizations on IR - Remove dead code - Refactor code - Improve performance Compiler Analysis Target Program

Slide 23

Slide 23 text

#hg2compose @mvndy_hd @jossiwolf .kt Lexer Analysis Syntax Analysis Generates tokens Build PSI tree Add info to nodes Analysis Resolution Phase Generate descriptors for PSI + Symbol Tables + + Enhance PSI elements w/ descriptors + Symbol table maps PSI to descriptor/IR calls Run static analysis, type verification, diagnostics Resolve + + Parsing Phase + + + + + + ● A compiler does two things with data: ○ Compiles - changes format ○ Lowers - simplifies data ● Compiler plugins can intercept compiler behavior at any phase The Kotlin Compiler IR Transformations Optimizing Phase < > Bytecode Codegen Phase Analyzes IR data to create additional logic - Call graph - Control-flow graph CPU-related IR Transformations FUNC VAL_PARAM VAL_PARAM VA:_PARAM Unoptimized IR - Performs additional analysis, transformations + optimisations specific to target CPU architecture - Analyzes IR optimized data to create CPU-related logic i.e. multithreading IR-to-Bytecode Generation Perform optimizations on IR - Remove dead code - Refactor code - Improve performance Compiler Analysis Target Program

Slide 24

Slide 24 text

.kt Lexer Analysis Syntax Analysis Generates tokens Kotlin Compiler Frontend Build PSI tree Add info to nodes Analysis Resolution Phase Generate descriptors for PSI + Symbol Tables + + Enhance PSI elements w/ descriptors + Symbol table maps PSI to descriptor/IR calls Run static analysis, type verification, diagnostics Resolve + + #hg2compose Parsing Phase + + + + + + @mvndy_hd @jossiwolf

Slide 25

Slide 25 text

@mvndy_hd @jossiwolf #hg2compose

Slide 26

Slide 26 text

#hg2compose IR Transformations Optimizing Phase < > Bytecode Codegen Phase Analyzes IR data to create additional logic - Call graph - Control-flow graph CPU-related IR Transformations FUNC VAL_PARAM VAL_PARAM VA:_PARAM Unoptimized IR - Performs additional analysis, transformations + optimisations specific to target CPU architecture - Analyzes IR optimized data to create CPU-related logic i.e. multithreading IR-to-Bytecode Generation Perform optimizations on IR - Remove dead code - Refactor code - Improve performance Compiler Analysis Target Program Kotlin Compiler Backend

Slide 27

Slide 27 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } #hg2compose @mvndy_hd @jossiwolf

Slide 28

Slide 28 text

@Composable fun HelloWorld(greeting: String, %composer: Composer?, %changed: Int) { %composer = %composer.startRestartGroup(.>) val %dirty = %changed if (%changed and 0b1110 ..= 0) { %dirty = %dirty or if (%composer.changed(greeting)) 0b0100 else 0b0010 } if (%dirty and 0b1011 xor 0b0010 ..= 0 .| !%composer.skipping) { Text("Hello, $greeting!") } else { %composer.skipToGroupEnd() } %composer.endRestartGroup() ..updateScope { %composer: Composer?, %force: Int .> HelloWorld(greeting, %composer, %changed or 0b0001) } } #hg2compose @mvndy_hd @jossiwolf

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Slot Table & Trees @mvndy_hd @jossiwolf #hg2compose Composition Slot Table Applier Virtual representation of the composition

Slide 31

Slide 31 text

var name by remember { mutableStateOf("Bob") } @Composable fun Greeting(greeting: String) { Text("Hello, $greeting!") } Greeting(name) name = "Jossi" ./ Hello, Bob! ./ Hello, Jossi! @mvndy_hd @jossiwolf Slot Table by Example #hg2compose

Slide 32

Slide 32 text

EMPTY EMPTY Group(4) State(“Bob”) var name by remember { mutableStateOf("Bob") } EMPTY EMPTY EMPTY EMPTY EMPTY @mvndy_hd @jossiwolf Slot Table by Example #hg2compose

Slide 33

Slide 33 text

Group(4) State(“Bob”) Group(5) var name by remember { mutableStateOf("Bob") } EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY @Composable fun Greeting(greeting: String) { } Greeting(name) @mvndy_hd @jossiwolf Slot Table by Example #hg2compose

Slide 34

Slide 34 text

EMPTY EMPTY Group(4) State(“Bob”) Group(5) Group(6) Group(7) Group(8) “Hello, Bob!” var name by remember { mutableStateOf("Bob") } @Composable fun Greeting(greeting: String) { Text("Hello, $greeting!”) } Greeting(name) @mvndy_hd @jossiwolf Slot Table by Example #hg2compose

Slide 35

Slide 35 text

EMPTY EMPTY Group(4) State(“Bob”) Group(5) Group(6) Group(7) Group(8) “Hello, Jossi!” var name by remember { mutableStateOf("Bob") } @Composable fun Greeting(greeting: String) { Text("Hello, $greeting!”) } Greeting(name) name = "Jossi" @mvndy_hd @jossiwolf Slot Table by Example #hg2compose

Slide 36

Slide 36 text

Structure @mvndy_hd @jossiwolf #hg2compose Composition Layout Drawing Applier

Slide 37

Slide 37 text

The UI Tree @mvndy_hd @jossiwolf #hg2compose LayoutNode LayoutNode LayoutNode

Slide 38

Slide 38 text

LayoutNode! ..and then what? D @mvndy_hd @jossiwolf #hg2compose

Slide 39

Slide 39 text

Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI Android Framework dispatchDraw()

Slide 40

Slide 40 text

Layout & Draw @mvndy_hd @jossiwolf #hg2compose class AndroidComposeView(context: Context) : ViewGroup(context), ... { override fun dispatchDraw(canvas: Canvas) { } }

Slide 41

Slide 41 text

Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI measureAndLayout() Android Framework dispatchDraw()

Slide 42

Slide 42 text

● Leland Richardson for his work and sitting down with us to interview him ● Zach Klippenstein's 5-part series on Snapshot State ● George Mount for his explanation of Compose UI fundamentals ● Matvei Malkov, Simona Stojanovic for reviews! ● Jorge Castillo, author of "Compose Internals" n n #hg2compose @mvndy_hd @jossiwolf Thank you!!

Slide 43

Slide 43 text

linktr.ee/hg2compose #hg2compose @mvndy_hd @jossiwolf

Slide 44

Slide 44 text

Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI measureAndLayout() Android Framework dispatchDraw() Platform-agnostic Platform-independent

Slide 45

Slide 45 text

@Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @jossiwolf Restartable Groups #hg2compose

Slide 46

Slide 46 text

@Composable fun HelloWorld(names: List) { names.forEach { name .> key(name) { Text(name) } } } Moveable Groups @mvndy_hd @jossiwolf #hg2compose

Slide 47

Slide 47 text

@Composable fun HelloWorld(name: String) { if (name .= "Amanda") { Text("Hello!") else if (name .= "Jossi") { Text("Hallo!") } else { Text("Uhm.. hi?") } } Replaceable Groups @mvndy_hd @jossiwolf #hg2compose