Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[DC SF 2022] Hitchhiking through Jetpack Compose

[DC SF 2022] Hitchhiking through Jetpack Compose

2fff9b69a69973e14026624f8c8a9672?s=128

Jossi Wolf

June 02, 2022
Tweet

More Decks by Jossi Wolf

Other Decks in Programming

Transcript

  1. Hitchhiking through Jetpack Compose Amanda Hinchman-Dominguez Kotlin GDE, Android Engineer

    @ Groupon Jossi Wolf Software Engineer @ Google #hg2compose
  2. @mvndy_hd @jossiwolf #hg2compose

  3. Why Compose? @mvndy_hd @jossiwolf #hg2compose

  4. @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”) } @mvndy_hd @jossiwolf #hg2compose
  5. "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
  6. 1 Snapshots 📸 data class Dog( val name: MutableState<String> )

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

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

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

    = "Fido" Dog(name = "Fido") snapshot.enter { dog.name.value = "Taco" } @mvndy_hd @jossiwolf #hg2compose
  10. 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
  11. 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
  12. - State changes cause recomposition - Compose has a Snapshot

    system Recap! @mvndy_hd @jossiwolf #hg2compose
  13. @mvndy_hd @jossiwolf #hg2compose

  14. 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
  15. #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
  16. Managing state in Compose @mvndy_hd @jossiwolf #hg2compose

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

    @jossiwolf
  18. @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
  19. @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @jossiwolf

    Restartable Groups #hg2compose
  20. @Composable fun HelloWorld(names: List<String>) { names.forEach { name .> key(name)

    { Text(name) } } } Moveable Groups @mvndy_hd @jossiwolf #hg2compose
  21. @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
  22. Groups are generated by the Compose compiler plugin #hg2compose @mvndy_hd

    @jossiwolf
  23. #hg2compose @mvndy_hd @jossiwolf The Kotlin Compiler

  24. #hg2compose @mvndy_hd @jossiwolf Compiler plugins can intercept compiler behavior at

    any phase
  25. .kt Lexer Analysis Syntax Analysis Parsing Phase 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 + + @mvndy_hd @jossiwolf #hg2compose
  26. @mvndy_hd @jossiwolf #hg2compose

  27. #hg2compose - Performs optimisations on IR - Removes dead code

    - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control-flow graph CPU-related IR Transformations IR Transform Compiler Analysis Middle End “Lower” Backend - Takes optimized IR and performs more analysis, transformations + optimisations specific to target CPU architecture - Multithreading < > Bytecode Target Program - Performs optimisations on IR - Removes dead code - Refactors the code - Improves performance - Analyzes IR for data needed to create - Call graph - Control-flow graph CPU-related IR Transformations FUNC VAL_PARAM VAL_PARAM VA:_PARAM Unoptimized IR @mvndy_hd @jossiwolf
  28. @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } #hg2compose @mvndy_hd

    @jossiwolf
  29. @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
  30. None
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. interface Applier<N> { val current: N fun onBeginChanges() {} fun

    onEndChanges() {} fun down(node: N) fun up() fun insertTopDown(index: Int, instance: N) fun insertBottomUp(index: Int, instance: N) fun remove(index: Int, count: Int) fun move(from: Int, to: Int, count: Int) fun clear() } The Applier @mvndy_hd @jossiwolf #hg2compose
  37. interface Applier<N> { val current: N fun onBeginChanges() {} fun

    onEndChanges() {} fun down(node: N) fun up() fun insertTopDown(index: Int, instance: N) fun insertBottomUp(index: Int, instance: N) fun remove(index: Int, count: Int) fun move(from: Int, to: Int, count: Int) fun clear() } The Applier @mvndy_hd @jossiwolf #hg2compose
  38. LayoutNode! ..and then what? @mvndy_hd @jossiwolf #hg2compose

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

  40. LayoutNode! ..and then what? @mvndy_hd @jossiwolf #hg2compose class AndroidComposeView(context: Context)

    : ViewGroup(context), ... { override fun dispatchDraw(canvas: Canvas) { ... measureAndLayout() ... } }
  41. • Jorge Castillo, author of "Compose Internals" • Leland Richardson

    for his work and sitting down with us to interview him • Zach Klippenstein's 5-part series on Compose state explained • George Mount for his explanation of Compose UI fundamentals • Matvei Malkov, Simona Stojanovic for reviews! #hg2compose @mvndy_hd @jossiwolf Thank you!!
  42. linktr.ee/hg2compose #hg2compose @mvndy_hd @jossiwolf