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

    View full-size slide

  2. @mvndy_hd @jossiwolf #hg2compose

    View full-size slide

  3. Why Compose?
    @mvndy_hd @jossiwolf #hg2compose

    View full-size slide

  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”)
    }

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  17. Managing state in
    Compose
    @mvndy_hd @jossiwolf #hg2compose

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  25. @mvndy_hd @jossiwolf #hg2compose

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  30. 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

    View full-size slide

  31. 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

    View full-size slide

  32. 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

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. Structure
    @mvndy_hd @jossiwolf #hg2compose
    Composition
    Layout
    Drawing
    Applier

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. ● 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!!

    View full-size slide

  42. linktr.ee/hg2compose
    #hg2compose
    @mvndy_hd @jossiwolf

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  46. @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

    View full-size slide