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

Opening the shutter on snapshots (dcNYC 22)

Opening the shutter on snapshots (dcNYC 22)

Video: https://www.droidcon.com/2022/09/29/opening-the-shutter-on-snapshots/

Jetpack Compose shows the power of a custom compiler plugin. But not all the magic happens during compilation. A lot of Compose features are based on a runtime library that doesn't require any compiler support: the snapshot system. It might seem like magic at first, but it's just built on top of things you might already know: ThreadLocals, linked lists, and, yes, even regular old callbacks. Once you understand how Compose thinks about state, you might find ways to use its tools in your own code – even outside of Compose.

Zach Klippenstein

September 02, 2022
Tweet

More Decks by Zach Klippenstein

Other Decks in Programming

Transcript

  1. Zach Klippenstein (he/him)


    Google
    Opening the shutter on snapshots

    View Slide

  2. @zachklipp
    Pre-watch: A Hitchhiker’s Guide to Compose


    Post-watch: Using Compose Runtime to create a client library
    not this talk
    this talk
    snapshots

    View Slide

  3. @zachklipp
    Pre-watch: A Hitchhiker’s Guide to Compose


    Post-watch: Using Compose Runtime to create a client library
    these slides my twitter
    not this talk
    this talk
    snapshots

    View Slide

  4. @zachklipp
    What for?


    What do they do?


    How do they work?


    What can we do with them?

    View Slide

  5. @zachklipp

    View Slide

  6. @zachklipp

    View Slide

  7. @zachklipp

    View Slide

  8. Snapshots are Git for your variables
    @zachklipp

    View Slide

  9. Composition is the release manager
    @zachklipp

    View Slide

  10. @zachklipp
    Git Snapshots
    branches snapshots


    main branch global snapshot


    commits state object writes


    merge apply snapshot


    resolve conflicts merge records


    hooks read, write, apply listeners

    View Slide

  11. @zachklipp
    ACID
    “In-memory”

    View Slide

  12. @zachklipp
    ACID


    tomicity
    On-disk
    “a”, Red
    “b”, Red “b”, Blue
    “b”, Blue

    View Slide

  13. @zachklipp
    A CID


    onsistency
    On-disk
    “a”
    “b”
    “c”
    “c” “c”
    tomicity

    View Slide

  14. @zachklipp
    AC ID


    solation
    On-disk
    “a”, Red
    “a”, Blue
    “b”, Red “b”, Blue
    onsistency
    urability

    View Slide

  15. @zachklipp
    ACI D
    urability
    On-disk
    solation

    View Slide

  16. @zachklipp
    ACI D
    urability
    On-disk
    In-memory
    solation

    View Slide

  17. @zachklipp
    ACID
    “In-memory”
    tomicity
    onsistency
    solation
    urability

    View Slide

  18. @zachklipp
    Database Snapshots
    ACID ACID


    transactional snapshots


    commit/rollback apply/dispose


    views derived state

    View Slide

  19. @zachklipp
    How do they work?

    View Slide

  20. @zachklipp

    View Slide

  21. @zachklipp

    View Slide

  22. No magic
    @zachklipp

    View Slide

  23. Compiler plugin?
    @zachklipp
    from A Hitchhiker’s Guide to Jetpack Compose

    View Slide

  24. No compiler plugin
    @zachklipp

    View Slide

  25. @zachklipp
    (but shipped with it)
    Snapshots ≠ Compose

    View Slide

  26. @zachklipp
    (but shipped with it)
    Snapshots ≠ Compose
    snapshots
    the rest of Compose
    ThreadLocal integers
    linked lists

    View Slide

  27. @zachklipp
    snapshots
    ThreadLocal integers
    linked lists

    View Slide

  28. @zachklipp
    Giant state container?
    (i.e. Redux)
    snapA
    “a”
    “foo”
    Range(0, 100)
    snapB
    “a”
    “bar”
    Range(0, 50)
    snapC
    “b”
    “foo”
    Range(25, 75)
    all
    state
    in app

    View Slide

  29. @zachklipp
    mutableStateOf
    mutableStateOf
    Range
    “a”
    “b”
    “foo”
    “bar”
    snapA
    snapB
    snapC

    View Slide

  30. @zachklipp
    Snapshots State Objects
    mutableStateOf
    mutableStateOf
    Range
    “a”
    “b”
    “foo”
    “bar”
    snapA
    snapB
    snapC

    View Slide

  31. @zachklipp
    Snapshots State Objects
    records
    mutableStateOf
    mutableStateOf
    Range
    “a”
    “b”
    “foo”
    “bar”
    snapA
    snapB
    snapC

    View Slide

  32. @zachklipp
    reads:
    96
    snapshots records
    128
    167
    181
    64
    75
    111
    209
    52
    53
    87
    many :
    state
    “foo”

    View Slide

  33. @zachklipp
    writes: :
    “bar”
    210
    96
    128
    167
    181
    64
    75
    111
    209
    52
    53
    87
    state
    “foo”

    View Slide

  34. @zachklipp
    Snapshots concepts State Objects
    low-level API


    concrete


    ID


    previous IDs


    invalid set
    primary API


    abstract


    map of IDs to values written
    by snapshot with ID


    i.e. “records”

    View Slide

  35. @zachklipp
    Snapshots API State Objects
    takeNestedSnapshot


    takeNestedMutableSnapshot


    withMutableSnapshot


    registerApplyObserver
    val state = mutableStateOf(“”)

    mutableStateListOf


    mutableStateMapOf


    …whatever!

    View Slide

  36. @zachklipp
    Snapshots operations State Objects
    Create:


    choose new ID


    Enter:


    set ThreadLocal


    restore ThreadLocal


    Apply:


    commit ID


    notify listeners
    Read:


    find record


    read from record


    Write:


    find or copy record


    write to record

    View Slide

  37. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  38. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  39. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  40. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  41. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  42. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  43. @zachklipp
    Selecting record for read
    message
    id value
    “he”
    “hello asdf” tombstoned
    “hello ” written by 72
    “hello world”
    color
    id value
    Red
    Blue written by 80
    snapshot ID invalid set

    View Slide

  44. newest valid record
    @zachklipp
    “mutableState.value” get() = findRecord(SnapshotThreadLocal.get())
    highest ID
    not higher than snapshot ID


    AND


    not in snapshot invalid set


    AND


    not tombstoned

    View Slide

  45. @zachklipp
    Git: commit SHAs

    View Slide

  46. @zachklipp
    Snapshot IDs

    View Slide

  47. @zachklipp
    Walkthrough: create child, apply
    val message = mutableStateOf("")


    val snapshot = Snapshot.takeMutableSnapshot()


    snapshot.enter {


    message.value = "hello"


    }


    snapshot.apply()
    message

    View Slide

  48. @zachklipp
    Walkthrough: create child, apply
    message
    val message = mutableStateOf("")
    global snapshot

    View Slide

  49. @zachklipp
    Walkthrough: create child, apply
    message
    val message = mutableStateOf("")
    global snapshot

    View Slide

  50. @zachklipp
    Walkthrough: create child, apply
    message
    val message = mutableStateOf("")
    “”
    global snapshot

    View Slide

  51. @zachklipp
    Walkthrough: create child, apply
    message
    val snapshot = Snapshot.takeMutableSnapshot()
    “”
    global snapshot
    snapshot

    View Slide

  52. @zachklipp
    Walkthrough: create child, apply
    message
    val snapshot = Snapshot.takeMutableSnapshot()
    “”
    global snapshot
    snapshot

    View Slide

  53. @zachklipp
    Walkthrough: create child, apply
    message
    val snapshot = Snapshot.takeMutableSnapshot()
    “”
    global snapshot
    snapshot

    View Slide

  54. @zachklipp
    Walkthrough: create child, apply
    message
    val snapshot = Snapshot.takeMutableSnapshot()
    “”
    global snapshot
    snapshot

    View Slide

  55. @zachklipp
    Walkthrough: create child, apply
    message
    val snapshot = Snapshot.takeMutableSnapshot()
    “”
    global snapshot
    snapshot

    View Slide

  56. @zachklipp
    Walkthrough: create child, apply
    message
    message.value = "hello"
    “”
    global snapshot
    snapshot

    View Slide

  57. @zachklipp
    Walkthrough: create child, apply
    message
    message.value = "hello"
    “hello”
    “”
    global snapshot
    snapshot

    View Slide

  58. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot
    snapshot

    View Slide

  59. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot
    snapshot

    View Slide

  60. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot

    View Slide

  61. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot

    View Slide

  62. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot

    View Slide

  63. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot

    View Slide

  64. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “”
    “hello”
    global snapshot

    View Slide

  65. @zachklipp
    Walkthrough: create child, apply
    message
    snapshot.apply()
    “hello”
    “”
    global snapshot

    View Slide

  66. @zachklipp
    Walkthrough: create child, apply
    message
    “hello”
    “”
    merged!
    global snapshot

    View Slide

  67. @zachklipp
    Snapshot rules:
    Create child


    Apply


    Advance

    View Slide

  68. @zachklipp
    Snapshot rules:
    Create child:


    1. Create new snapshot


    2. Copy parent’s invalid list


    3. Advance parent – child’s ID will be added to invalid set (see Advance rule 3 below)


    Apply:


    1. Advance parent


    2. Remove child ID from invalid set – parent sees child’s writes


    3. Add child’s current and previous IDs to parent’s list of previous IDs – parent “absorbs” child


    Advance:


    1. New ID is next highest ID


    2. Add old ID to list of previous IDs


    3. Add IDs between old and new (exclusive) to invalid set


    This is a superset of all open snapshots


    i.e. Unrelated snapshots created since old ID should be ignored

    View Slide

  69. @zachklipp
    Walkthrough: create 2 children, apply
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)


    val snapshotA = Snapshot.takeMutableSnapshot()


    val snapshotB = Snapshot.takeMutableSnapshot()


    snapshotA.enter {


    message.value = "hello"


    }


    snapshotB.enter {


    color.value = Blue


    }


    snapshotA.apply()


    snapshotB.apply()

    View Slide

  70. @zachklipp
    Walkthrough: create 2 children, apply
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)
    message
    color
    global snapshot

    View Slide

  71. @zachklipp
    Walkthrough: create 2 children, apply
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)
    message
    “”
    color
    Red
    global snapshot

    View Slide

  72. @zachklipp
    Walkthrough: create 2 children, apply
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot

    View Slide

  73. @zachklipp
    Walkthrough: create 2 children, apply
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot

    View Slide

  74. @zachklipp
    Walkthrough: create 2 children, apply
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot

    View Slide

  75. @zachklipp
    Walkthrough: create 2 children, apply
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot

    View Slide

  76. @zachklipp
    Walkthrough: create 2 children, apply
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot

    View Slide

  77. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  78. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  79. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  80. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  81. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  82. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    val snapshotB = Snapshot.takeMutableSnapshot()
    snapshotB

    View Slide

  83. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    snapshotA.enter { message.value = "hello" }

    View Slide

  84. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    snapshotA.enter { message.value = "hello" }

    View Slide

  85. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    snapshotA.enter { message.value = "hello" }

    View Slide

  86. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    snapshotB.enter { color.value = Blue }

    View Slide

  87. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    snapshotB.enter { color.value = Blue }

    View Slide

  88. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotB.enter { color.value = Blue }

    View Slide

  89. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotA.apply()

    View Slide

  90. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotA.apply()

    View Slide

  91. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotA.apply()

    View Slide

  92. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotA.apply()

    View Slide

  93. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB
    “hello”
    Blue
    snapshotA.apply()

    View Slide

  94. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotA.apply()
    snapshotB
    “hello”
    Blue

    View Slide

  95. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotA.apply()
    snapshotB
    “hello”
    Blue

    View Slide

  96. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    snapshotB
    “hello”
    Blue

    View Slide

  97. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    snapshotB
    “hello”
    Blue

    View Slide

  98. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    “hello”
    Blue

    View Slide

  99. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    “hello”
    Blue

    View Slide

  100. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    “hello”
    Blue

    View Slide

  101. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    “hello”
    Blue

    View Slide

  102. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    snapshotB.apply()
    “hello”
    Blue

    View Slide

  103. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    snapshotB.apply()

    View Slide

  104. @zachklipp
    Walkthrough: create 2 children, apply
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    merged!

    View Slide

  105. @zachklipp
    Walkthrough: created nested child, apply
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)


    val snapshotA = Snapshot.takeMutableSnapshot()


    snapshotA.enter {


    message.value = "hello"


    val snapshotB = Snapshot.takeMutableSnapshot()


    snapshotB.enter {


    color.value = Blue


    }


    snapshotB.apply()


    }


    snapshotA.apply()

    View Slide

  106. @zachklipp
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)
    message
    color
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  107. @zachklipp
    val message = mutableStateOf("")


    val color = mutableStateOf(Red)
    message
    “”
    color
    Red
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  108. @zachklipp
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  109. @zachklipp
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  110. @zachklipp
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  111. @zachklipp
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  112. @zachklipp
    val snapshotA = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  113. @zachklipp
    snapshotA.enter { message.value = "hello"
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  114. @zachklipp
    snapshotA.enter { message.value = "hello"
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    Walkthrough: created nested child, apply

    View Slide

  115. @zachklipp
    snapshotA.enter { message.value = "hello"
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  116. @zachklipp
    snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  117. @zachklipp
    snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  118. @zachklipp
    snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  119. @zachklipp
    snapshotA.enter { val snapshotB = Snapshot.takeMutableSnapshot()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  120. @zachklipp
    snapshotA.enter { snapshotB.enter { color.value = Blue }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  121. @zachklipp
    snapshotA.enter { snapshotB.enter { color.value = Blue }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Walkthrough: created nested child, apply

    View Slide

  122. @zachklipp
    snapshotA.enter { snapshotB.enter { color.value = Blue }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  123. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  124. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    snapshotB
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  125. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  126. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  127. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  128. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  129. @zachklipp
    snapshotA.enter { snapshotB.apply() }
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  130. @zachklipp
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    snapshotA.apply()
    Walkthrough: created nested child, apply

    View Slide

  131. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    snapshotA
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  132. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  133. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  134. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  135. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  136. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  137. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  138. @zachklipp
    snapshotA.apply()
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    Walkthrough: created nested child, apply

    View Slide

  139. @zachklipp
    message
    “”
    color
    Red
    global snapshot
    “hello”
    Blue
    merged!
    Walkthrough: created nested child, apply

    View Slide

  140. @zachklipp
    MutableState
    message
    “”
    “hello”
    interface MutableState : State {


    View Slide

  141. @zachklipp
    MutableState
    message
    “”
    “hello”
    interface MutableState : State {


    override var value: T


    operator fun component1(): T


    operator fun component2(): (T) -> Unit


    }

    View Slide

  142. @zachklipp
    SnapshotMutableState MutableState
    message
    “”
    “hello”
    interface SnapshotMutableState : MutableState {


    val policy: SnapshotMutationPolicy


    }
    interface MutableState : State {


    View Slide

  143. @zachklipp
    SnapshotMutableStateImpl SnapshotMutableState
    message
    “”
    “hello”
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    }
    interface SnapshotMutableState : MutableState {


    View Slide

  144. @zachklipp
    SnapshotMutableStateImpl StateObject
    message
    “”
    “hello”
    internal class SnapshotMutableStateImpl(


    val firstStateRecord: StateRecord


    fun prependStateRecord(value: StateRecord)


    fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? = null


    }
    interface SnapshotMutableState : MutableState {


    View Slide

  145. @zachklipp
    message
    “”
    “hello”
    interface StateObject {


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    }
    SnapshotMutableStateImpl StateObject
    :

    View Slide

  146. @zachklipp
    message
    “”
    “hello”
    interface StateObject {


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO("???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    }
    SnapshotMutableStateImpl StateObject
    :

    View Slide

  147. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO("???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    }

    View Slide

  148. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO("???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = TODO("???")


    override fun assign(value: StateRecord) {


    TODO("???")


    }


    }


    }

    View Slide

  149. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO("???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    TODO("???")


    }


    }


    }

    View Slide

  150. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO("???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  151. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = TODO(“???")


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  152. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    TODO("???")


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  153. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = TODO("???")


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as StateStateRecord


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  154. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = next.value


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as StateStateRecord


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  155. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = next.readable(this).value


    set(value) { TODO("???") }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as StateStateRecord


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  156. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    private var next: StateStateRecord = StateStateRecord(value)


    override var value: T


    get() = next.readable(this).value


    set(value) {


    next.writable(this) {


    this.value = value


    }


    }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as StateStateRecord


    }


    private class StateStateRecord(myValue: T) : StateRecord() {


    var value: T = myValue


    override fun create(): StateRecord = StateStateRecord(value)


    override fun assign(value: StateRecord) {


    this.value = (value as StateStateRecord).value


    }


    }


    }

    View Slide

  157. @zachklipp
    internal class SnapshotMutableStateImpl(


    val firstStateRecord: StateRecord


    fun prependStateRecord(value: StateRecord)


    fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? = null


    }

    View Slide

  158. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    }


    }

    View Slide

  159. @zachklipp
    internal class SnapshotMutableStateImpl(


    value: T,


    override val policy: SnapshotMutationPolicy


    ) : StateObject, SnapshotMutableState {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    val previousRecord = previous as StateStateRecord


    val currentRecord = current as StateStateRecord


    val appliedRecord = applied as StateStateRecord


    return if (policy.equivalent(currentRecord.value, appliedRecord.value))


    current


    else {


    val merged = policy.merge(


    previousRecord.value,


    currentRecord.value,


    appliedRecord.value


    )


    if (merged != null) {


    appliedRecord.create().also {


    (it as StateStateRecord).value = merged


    }


    } else {


    null


    }


    }


    }


    }

    View Slide

  160. @zachklipp
    What can we do with them?

    View Slide

  161. @zachklipp
    Custom state objects
    class Range {


    var start: Int = 0


    var end: Int = 0


    }

    View Slide

  162. @zachklipp
    Custom state objects
    class Range {


    private var next = Record()


    var start: Int = 0


    var end: Int = 0


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  163. @zachklipp
    Custom state objects
    class Range {


    private var next = Record()


    var start: Int


    get() = next.readable(this).start


    set(value) {


    next.writable(this) { start = value }


    }


    var end: Int


    get() = next.readable(this).end


    set(value) {


    next.writable(this) { end = value }


    }


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  164. @zachklipp
    Custom state objects
    class Range : StateObject {


    private var next = Record()


    var start: Int


    get() = next.readable(this).start


    set(value) {


    next.writable(this) { start = value }


    }


    var end: Int


    get() = next.readable(this).end


    set(value) {


    next.writable(this) { end = value }


    }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as Record


    }


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  165. @zachklipp
    Custom state objects
    class Range : StateObject {


    private var next = Record()


    var start: Int


    get() = next.readable(this).start


    set(value) {


    next.writable(this) { start = value }


    }


    var end: Int


    get() = next.readable(this).end


    set(value) {


    next.writable(this) { end = value }


    }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as Record


    }


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  166. @zachklipp
    Custom state objects
    class Range : StateObject {


    private var next = Record()


    var start: Int


    get() = next.readable(this).start


    set(value) {


    require(value <= next.readable(this).end)


    next.writable(this) { start = value }


    }


    var end: Int


    get() = next.readable(this).end


    set(value) {


    require(value >= next.readable(this).start)


    next.writable(this) { end = value }


    }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as Record


    }


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  167. @zachklipp
    Custom state objects
    class Range : StateObject {


    private var next = Record()


    var start: Int


    get() = next.readable(this).start


    set(value) {


    next.withCurrent { require(value <= it.end) }


    next.writable(this) { start = value }


    }


    var end: Int


    get() = next.readable(this).end


    set(value) {


    next.withCurrent { require(value >= it.start) }


    next.writable(this) { end = value }


    }


    override val firstStateRecord: StateRecord


    get() = next


    override fun prependStateRecord(value: StateRecord) {


    next = value as Record


    }


    private class Record : StateRecord() {


    var start: Int = 0


    var end: Int = 0


    override fun create(): StateRecord = Record()


    override fun assign(value: StateRecord) {


    value as Record


    start = value.start


    end = value.end


    }


    }


    }

    View Slide

  168. @zachklipp
    Custom state objects
    class Range : StateObject {


    }

    View Slide

  169. @zachklipp
    Custom state objects
    class Range : StateObject {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    }


    }

    View Slide

  170. @zachklipp
    Custom state objects
    class Range : StateObject {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    val previousRecord = previous as Record


    val currentRecord = current as Record


    val appliedRecord = applied as Record


    if (currentRecord.start == appliedRecord.start &&


    currentRecord.end == appliedRecord.end


    ) {


    return current


    }


    }


    }

    View Slide

  171. @zachklipp
    Custom state objects
    class Range : StateObject {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    val previousRecord = previous as Record


    val currentRecord = current as Record


    val appliedRecord = applied as Record


    if (currentRecord.start == appliedRecord.start &&


    currentRecord.end == appliedRecord.end


    ) {


    return current


    }


    val currentChangedStart = previousRecord.start != currentRecord.start


    val currentChangedEnd = previousRecord.end != currentRecord.end


    val appliedChangedStart = previousRecord.start != appliedRecord.start


    val appliedChangedEnd = previousRecord.end != appliedRecord.end


    if ((currentChangedStart && appliedChangedStart) ||


    (currentChangedEnd && appliedChangedEnd)


    ) {


    return null


    }


    }


    }

    View Slide

  172. @zachklipp
    Custom state objects
    class Range : StateObject {


    override fun mergeRecords(


    previous: StateRecord,


    current: StateRecord,


    applied: StateRecord


    ): StateRecord? {


    val previousRecord = previous as Record


    val currentRecord = current as Record


    val appliedRecord = applied as Record


    if (currentRecord.start == appliedRecord.start &&


    currentRecord.end == appliedRecord.end


    ) {


    return current


    }


    val currentChangedStart = previousRecord.start != currentRecord.start


    val currentChangedEnd = previousRecord.end != currentRecord.end


    val appliedChangedStart = previousRecord.start != appliedRecord.start


    val appliedChangedEnd = previousRecord.end != appliedRecord.end


    if ((currentChangedStart && appliedChangedStart) ||


    (currentChangedEnd && appliedChangedEnd)


    ) {


    return null


    }


    return Record().apply {


    start = if (currentChangedStart) currentRecord.start else appliedRecord.start


    end = if (currentChangedEnd) currentRecord.end else appliedRecord.end


    }


    }


    }

    View Slide

  173. @zachklipp
    State diffing
    github.com/zach-klippenstein/compose-autotransition

    View Slide

  174. @zachklipp
    State diffing
    github.com/zach-klippenstein/compose-autotransition

    View Slide

  175. @zachklipp
    State diffing
    suspend fun withAnimation(changes: () -> Unit) {


    val statesToAnimate = mutableSetOf()


    val targetValues = mutableMapOf()


    val snapshot = Snapshot.takeMutableSnapshot(


    writeObserver = { stateObject -> statesToAnimate += stateObject }


    )


    snapshot.enter {


    changes()


    statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value }


    }


    snapshot.dispose()


    coroutineScope {


    statesToAnimate.forEach { stateObject ->


    launch {


    animate(stateObject.value, targetValues[stateObject])


    }


    }


    }


    }

    View Slide

  176. @zachklipp
    State diffing
    suspend fun withAnimation(changes: () -> Unit) {


    val statesToAnimate = mutableSetOf()


    val targetValues = mutableMapOf()


    val snapshot = Snapshot.takeMutableSnapshot(


    writeObserver = { stateObject -> statesToAnimate += stateObject }


    )


    snapshot.enter {


    changes()


    statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value }


    }


    snapshot.dispose()


    coroutineScope {


    statesToAnimate.forEach { stateObject ->


    launch {


    animate(stateObject.value, targetValues[stateObject])


    }


    }


    }


    }

    View Slide

  177. @zachklipp
    State diffing
    suspend fun withAnimation(changes: () -> Unit) {


    val statesToAnimate = mutableSetOf()


    val targetValues = mutableMapOf()


    val snapshot = Snapshot.takeMutableSnapshot(


    writeObserver = { stateObject -> statesToAnimate += stateObject }


    )


    snapshot.enter {


    changes()


    statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value }


    }


    snapshot.dispose()


    coroutineScope {


    statesToAnimate.forEach { stateObject ->


    launch {


    animate(stateObject.value, targetValues[stateObject])


    }


    }


    }


    }

    View Slide

  178. @zachklipp
    State diffing
    suspend fun withAnimation(changes: () -> Unit) {


    val statesToAnimate = mutableSetOf()


    val targetValues = mutableMapOf()


    val snapshot = Snapshot.takeMutableSnapshot(


    writeObserver = { stateObject -> statesToAnimate += stateObject }


    )


    snapshot.enter {


    changes()


    statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value }


    }


    snapshot.dispose()


    coroutineScope {


    statesToAnimate.forEach { stateObject ->


    launch {


    animate(stateObject.value, targetValues[stateObject])


    }


    }


    }


    }

    View Slide

  179. @zachklipp
    State diffing
    suspend fun withAnimation(changes: () -> Unit) {


    val statesToAnimate = mutableSetOf()


    val targetValues = mutableMapOf()


    val snapshot = Snapshot.takeMutableSnapshot(


    writeObserver = { stateObject -> statesToAnimate += stateObject }


    )


    snapshot.enter {


    changes()


    statesToAnimate.forEach { stateObject -> targetValues[stateObject] = stateObject.value }


    }


    snapshot.dispose()


    coroutineScope {


    statesToAnimate.forEach { stateObject ->


    launch {


    animate(stateObject.value, targetValues[stateObject])


    }


    }


    }


    }

    View Slide

  180. @zachklipp
    State diffing
    github.com/zach-klippenstein/compose-autotransition

    View Slide

  181. @zachklipp
    State diffing
    github.com/zach-klippenstein/compose-autotransition

    View Slide

  182. @zachklipp
    Persistence
    “In-memory”
    tomicity
    onsistency
    solation
    urability
    ACID

    View Slide

  183. @zachklipp
    Persistence
    “In-memory”
    tomicity
    onsistency
    solation
    urability
    ACID

    View Slide

  184. @zachklipp
    Persistence
    fun observeUserAsState(name: String): State {


    val cached = cachedStates.getOrPut(name) {


    val flow = users.findByName(name)


    val state = mutableStateOf(null)


    val job = scope.launch {


    flow.collect {


    state.value = it


    }


    }


    return@getOrPut UserState(state, job)


    }


    return cached.state


    }
    Read

    View Slide

  185. @zachklipp
    Persistence
    Read
    fun observeUserAsState(name: String): State {


    val cached = cachedStates.getOrPut(name) {


    val flow = users.findByName(name)


    val state = mutableStateOf(null)


    val job = scope.launch {


    flow.collect {


    state.value = it


    }


    }


    return@getOrPut UserState(state, job)


    }


    return cached.state


    }

    View Slide

  186. @zachklipp
    Persistence
    Read
    fun observeUserAsState(name: String): State {


    val cached = cachedStates.getOrPut(name) {


    val flow = users.findByName(name)


    val state = mutableStateOf(null)


    val job = scope.launch {


    flow.collect {


    state.value = it


    }


    }


    return@getOrPut UserState(state, job)


    }


    return cached.state


    }

    View Slide

  187. @zachklipp
    Persistence
    Read
    fun observeUserAsState(name: String): State {


    val cached = cachedStates.getOrPut(name) {


    val flow = users.findByName(name)


    val state = mutableStateOf(null)


    val job = scope.launch {


    flow.collect {


    state.value = it


    }


    }


    return@getOrPut UserState(state, job)


    }


    return cached.state


    }

    View Slide

  188. @zachklipp
    Persistence
    Read
    fun observeUserAsState(name: String): State {


    val cached = cachedStates.getOrPut(name) {


    val flow = users.findByName(name)


    val state = mutableStateOf(null)


    val job = scope.launch {


    flow.collect {


    state.value = it


    }


    }


    return@getOrPut UserState(state, job)


    }


    return cached.state


    }

    View Slide

  189. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  190. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  191. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  192. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  193. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  194. @zachklipp
    Persistence
    Update
    Snapshot.registerApplyObserver { changedStateObjects, snapshot ->


    val changedDbStates = changedStateObjects.mapNotNull {


    if (it in cachedStates) it as MutableState else null


    }




    if (changedDbStates.isNotEmpty()) {


    scope.launch(Dispatchers.IO) {


    val readSnapshot = snapshot.takeNestedSnapshot()


    try {


    readSnapshot.enter {


    changedDbStates.forEach { (user, _) ->


    user?.let { users.updateUser(user) }


    }


    }


    } finally {


    readSnapshot.dispose()


    }


    }


    }


    }

    View Slide

  195. @zachklipp
    Persistence
    Usage
    @Composable


    fun UserNameEditor(database: SnapshotDatabase) {


    val userState = remember(database) {


    database.observeUserAsState("Rhaenyra")


    }


    userState.value?.let { user ->


    BasicTextField(


    value = user.name,


    onValueChange = {


    userState.value = user.copy(name = it)


    }


    )


    }


    }

    View Slide

  196. @zachklipp
    Persistence
    Usage
    @Composable


    fun UserNameEditor(database: SnapshotDatabase) {


    val userState = remember(database) {


    database.observeUserAsState("Rhaenyra")


    }


    userState.value?.let { user ->


    BasicTextField(


    value = user.name,


    onValueChange = {


    userState.value = user.copy(name = it)


    }


    )


    }


    }

    View Slide

  197. Snapshots are Git for your variables
    @zachklipp

    View Slide

  198. Further reading:
    More info about implementing custom StateObjects:
    Slides:
    Whitepaper on multiversion concurrency control:
    @zachklipp

    View Slide

  199. Questions?
    twitter.com/ @zachklipp
    More info about implementing custom StateObjects:
    Slides:
    Whitepaper on multiversion concurrency control:

    View Slide