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

DC SF - Hitchhiking Through Jetpack Compose

Jossi Wolf
September 01, 2022

DC SF - Hitchhiking Through Jetpack Compose

Jossi Wolf

September 01, 2022
Tweet

More Decks by Jossi Wolf

Other Decks in Programming

Transcript

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

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

  3. Why Compose? @mvndy_hd @jossiwolf #hg2compose

  4. @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”) }
  5. Compose Phases @mvndy_hd @jossiwolf #hg2compose Composition Layout Drawing Compiler

  6. "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
  7. 1 Snapshots 📸 data class Dog( val name: MutableState<String> )

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

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

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

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

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

    system - Snapshots power state Recap! @mvndy_hd @jossiwolf #hg2compose
  15. 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
  16. #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
  17. Managing state in Compose @mvndy_hd @jossiwolf #hg2compose

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

    @jossiwolf Generating Groups in Compose
  19. @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
  20. Restartable Moveable Replaceable @mvndy_hd @jossiwolf Types of Groups #hg2compose

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

    @jossiwolf
  22. #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
  23. #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
  24. .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
  25. @mvndy_hd @jossiwolf #hg2compose

  26. #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
  27. @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } #hg2compose @mvndy_hd

    @jossiwolf
  28. @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
  29. None
  30. Slot Table & Trees @mvndy_hd @jossiwolf #hg2compose Composition Slot Table

    Applier Virtual representation of the composition
  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. Structure @mvndy_hd @jossiwolf #hg2compose Composition Layout Drawing Applier

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

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

  39. Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI Android

    Framework dispatchDraw()
  40. Layout & Draw @mvndy_hd @jossiwolf #hg2compose class AndroidComposeView(context: Context) :

    ViewGroup(context), ... { override fun dispatchDraw(canvas: Canvas) { } }
  41. Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI measureAndLayout()

    Android Framework dispatchDraw()
  42. • 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!!
  43. linktr.ee/hg2compose #hg2compose @mvndy_hd @jossiwolf

  44. Layout & Draw @mvndy_hd @jossiwolf #hg2compose AndroidComposeView Compose UI measureAndLayout()

    Android Framework dispatchDraw() Platform-agnostic Platform-independent
  45. @Composable fun HelloWorld(greeting: String) { Text("Hello, $greeting!") } @mvndy_hd @jossiwolf

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

    { Text(name) } } } Moveable Groups @mvndy_hd @jossiwolf #hg2compose
  47. @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