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 Slide

  2. @mvndy_hd @jossiwolf #hg2compose

    View Slide

  3. Why Compose?
    @mvndy_hd @jossiwolf #hg2compose

    View 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 Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 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 Slide

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

    View 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 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 Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View 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 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 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 Slide

  25. @mvndy_hd @jossiwolf #hg2compose

    View 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 Slide

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

    View 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 Slide

  29. View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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!!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide