$30 off During Our Annual Pro Sale. View Details »

[DC SF 2022] Hitchhiking through Jetpack Compose

[DC SF 2022] Hitchhiking through Jetpack Compose

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

    View Slide

  2. @mvndy_hd @jossiwolf #hg2compose

    View Slide

  3. Why Compose?
    @mvndy_hd @jossiwolf #hg2compose

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  12. - State changes cause recomposition
    - Compose has a Snapshot system
    Recap!
    @mvndy_hd @jossiwolf #hg2compose

    View Slide

  13. @mvndy_hd @jossiwolf #hg2compose

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  23. #hg2compose
    @mvndy_hd @jossiwolf
    The Kotlin Compiler

    View Slide

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

    View Slide

  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

    View Slide

  26. @mvndy_hd @jossiwolf #hg2compose

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  30. 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. interface Applier {
    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

    View Slide

  37. interface Applier {
    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

    View Slide

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

    View Slide

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

    View Slide

  40. LayoutNode! ..and then what?
    @mvndy_hd @jossiwolf #hg2compose
    class AndroidComposeView(context: Context) :
    ViewGroup(context), ... {
    override fun dispatchDraw(canvas: Canvas) {
    ...
    measureAndLayout()
    ...
    }
    }

    View Slide

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

    View Slide

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

    View Slide