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

Deriving derived state: derivedStateOf explaine...

Deriving derived state: derivedStateOf explained (dcSF 24)

Jetpack Compose’s state library has a powerful but tricky tool in derivedStateOf. Much of the time you don’t need it at all. It’s deceptively expensive to use and complex to implement. To find out why we’ll derive it from first principles. Starting with the simplest solution for a typical use case, we’ll walk through how different parts of the snapshot state system can be leveraged to improve the solution, eventually ending up at derivedStateOf.

Zach Klippenstein

June 07, 2024
Tweet

More Decks by Zach Klippenstein

Other Decks in Programming

Transcript

  1. val state by remember { derivedStateOf { ... } }

    fun <T> derivedStateOf( calculation: () -> T ): State<T>
  2. Questions? Opening the Shutter on Snapshots: bit.ly/3x5zH53 Slides: bit.ly/45cqHYl [email protected]

    Snapshots blog series: bit.ly/3KAPprT androiddev.social/@zachklipp
  3. @Composable fun Foo() { val state1 = remember { mutableStateOf(0)

    } val state2 = remember { mutableStateOf(1) } val result = state1.value + state2.value Text(result.toString()) } Don't use derivedStateOf!
  4. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Cheap calculation, changes frequently
  5. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Cheap calculation, changes frequently
  6. @Composable fun Foo() { val state1 = remember { mutableStateOf(0)

    } val state2 = remember { mutableStateOf(1) } val result = state1.value + state2.value Text(result.toString()) }
  7. @Composable fun Foo() { val state1 = remember { mutableStateOf(0)

    } val state2 = remember { mutableStateOf(1) } val result = 0 < state1.value + state2.value Text(result.toString()) }
  8. @Composable fun Foo() { val state1 = remember { mutableStateOf(0)

    } val state2 = remember { mutableStateOf(1) } val result = derivedStateOf { 0 < state1.value + state2.value } Text(result.value.toString()) }
  9. @Composable fun Foo() { val state1 = remember { mutableStateOf(0)

    } val state2 = remember { mutableStateOf(1) } val result = remember { derivedStateOf { 0 < state1.value + state2.value } } Text(result.value.toString()) } Only re-composes when result actually changes
  10. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Cheap calculation, used infrequently
  11. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Cheap calculation, used infrequently
  12. @Composable fun Foo(items: List<Bar>) { val sortedItems = items.sorted() LazyColumn(

    Modifier.draw { sortedItems.forEach { drawDecoration(it) } } ) { items(sortedItems) { ... } } } Not used until layout or draw Expensive call in composition
  13. @Composable fun Foo(items: List<Bar>) { val sortedItems = remember {

    { items.sorted() } } LazyColumn( Modifier.draw { sortedItems.forEach { drawDecoration(it) } } ) { items(sortedItems) { ... } } }
  14. @Composable fun Foo(items: List<Bar>) { val sortedItems = remember {

    { items.sorted() } } LazyColumn( Modifier.draw { sortedItems().forEach { drawDecoration(it) } } ) { items(sortedItems()) { ... } } } sorted called lazily, but still called twice per frame
  15. @Composable fun Foo(items: List<Bar>) { val sortedItems = remember {

    derivedStateOf { items.sorted() } } LazyColumn( Modifier.draw { sortedItems().forEach { drawDecoration(it) } } ) { items(sortedItems()) { ... } } }
  16. @Composable fun Foo(items: List<Bar>) { val sortedItems = remember {

    derivedStateOf { items.sorted() } } LazyColumn( Modifier.draw { sortedItems.value.forEach { drawDecoration(it) } } ) { items(sortedItems.value) { ... } } }
  17. @Composable fun Foo(items: List<Bar>) { val updatedItems = rememberUpdatedState(items) val

    sortedItems = remember { derivedStateOf { updatedItems.value.sorted() } } LazyColumn( Modifier.draw { sortedItems.value.forEach { drawDecoration(it) } } ) { items(sortedItems.value) { ... } } } Not executed until first used, in layout Then re-used in draw
  18. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Expensive calculation, changes infrequently, used frequently
  19. Frame 1 Frame 2 Frame 3 Frame 4 Frame 5

    Frame 6 Frame 7 Calculation derivedStateOf Render Expensive calculation, changes infrequently, used frequently
  20. @Composable fun rememberFoo(input: Input): State<Output> { return remember(input) { derivedStateOf

    { someCalculation(input) } } } BAD Returning a new value each time Capturing value, not state of value
  21. @Composable fun rememberFoo(input: Input): Output { return remember(input) { someCalculation(input)

    } } When someCalculation: is used immediately in composition changes frequently with input
  22. @Composable fun rememberFoo(input: Input): State<Output> { val updatedInput = rememberUpdatedState(input)

    return remember { derivedStateOf { someCalculation(updatedInput) } } } When someCalculation: is expensive and not read in composition changes infrequently with input
  23. Function Invalidations = 1. Marked as needing to rerun 2.

    Scheduled to rerun in the future 3. Eventually ran again
  24. “Invalidation scopes”: Composable functions val state = mutableStateOf("") @Composable fun

    Foo() { val bar = remember { ... } val value = state.value Text(value) } Invalidations Entire body of reader recomposes
  25. “Invalidation scopes”: (Some) Composable functions val state = mutableStateOf("") @Composable

    fun Foo() { val bar = remember { ... } val value = state.value Text(value) } Invalidations
  26. “Invalidation scopes”: (Some) Composable functions val state = mutableStateOf("") @Composable

    fun Foo() { val bar = remember { ... } val value = state.value Text(value) } @Composable fun rememberBar(): Bar { // ... } Invalidations
  27. “Invalidation scopes”: (Some) Composable functions val state = mutableStateOf("") @Composable

    fun Foo() { val bar = remember { ... } val value = state.value Text(value) } @Composable fun rememberBar(): Bar { // ... } @Composable inline fun Baz() { // ... } Invalidations
  28. “Invalidation scopes”: (Some) Composable functions snapshotFlow layout, draw, graphicsLayer, etc.

    Invalidations Layout(content) { measurable, constraints -> // … } Modifier.graphicsLayer { // … } Modifier.draw { // … } // etc.
  29. “Invalidation scopes”: (Some) Composable functions snapshotFlow layout, draw, graphicsLayer, etc.

    SnapshotStateObserver val state = mutableStateOf("") val observer = SnapshotStateObserver { it() } observer.observeReads(scope, onValueChanged) { state.value } Invalidations
  30. “Invalidation scopes”: (Some) Composable functions snapshotFlow layout, draw, graphicsLayer, etc.

    SnapshotStateObserver derivedStateOf val state = mutableStateOf("") val derivedState = derivedStateOf { state.value } Invalidations
  31. Invalidations Based on snapshot state Two phases: 1. Know what

    state objects were read 2. Listen for when those objects are written later
  32. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() readSet Invalidations
  33. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { val result = state1.value + state2.value println(result) } readSet Invalidations
  34. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet Invalidations
  35. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet Invalidations
  36. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 Invalidations
  37. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 Invalidations
  38. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 state2 Invalidations
  39. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 state2 Invalidations
  40. Read observation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) // "0" } } readSet state1 state2 Invalidations
  41. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() readSet state1 state2 Invalidations
  42. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } readSet state1 state2 Invalidations
  43. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } readSet state1 state2 changedObjects Invalidations
  44. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects readSet state1 state2 Invalidations
  45. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects state1 readSet state1 state2 Invalidations
  46. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects state1 readSet state1 state2 Invalidations
  47. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects state1 state2 readSet state1 state2 Invalidations
  48. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects state1 state2 readSet state1 state2 Invalidations
  49. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } readSet state1 state2 changedObjects state1 state2 Invalidations
  50. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() readSet state1 state2 Invalidations
  51. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 state2 Invalidations
  52. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet state1 state2 Invalidations
  53. Read invalidation: Normal state val state1 = mutableStateOf(0) val state2

    = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } readSet Invalidations
  54. val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val readSet

    = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) // "0" } } Read invalidation: Normal state (no optimization) readSet state1 state2 Invalidations
  55. val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val readSet

    = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { val result = state1.value + state2.value println(result) } } Read observation: Derived state (no optimization) Invalidations
  56. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { val result = derivedStateOf { state1.value + state2.value } println(result.value) } } Invalidations
  57. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } Invalidations
  58. Read observation: Derived state (no optimization) readSet val state1 =

    mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } Invalidations
  59. Read observation: Derived state (no optimization) readSet result val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } Invalidations
  60. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } readSet result Invalidations
  61. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } readSet result state1 Invalidations
  62. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } readSet result state1 Invalidations
  63. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } readSet result state1 state2 Invalidations
  64. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) // "0" } } readSet result state1 state2 Invalidations
  65. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changedObjects state1 state2 readSet result state1 state2 Invalidations
  66. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() Snapshot.registerApplyObserver { changedObjects, _ -> if (changedObjects.any { it in readSet }) { schedulePrintResult() } } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } readSet result state1 state2 changedObjects state1 state2 Invalidations
  67. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) } } readSet result state1 state2 Invalidations
  68. Read observation: Derived state (no optimization) val state1 = mutableStateOf(0)

    val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } val readSet = mutableSetOf<Any>() fun printResult() { readSet.clear() Snapshot.observe( readObserver = { readSet += it } ) { println(result.value) // "0" } } ☹ readSet result state1 state2 Invalidations
  69. Read observation: Derived state (with optimization) Two phases: 1. Know

    what state objects were read 2. Listen for when those objects are written later Invalidations
  70. Dedup invalidations Multiple phases: Know what state objects were read

    Know what derivedState dependencies were used Know each derivedState’s calculation value Listen for when those objects are written later Check new calculation result, only invalidate if different Read observation: Derived state (with optimization)
  71. Dedup invalidations derivedStateOf tracks its own dependencies Readers query for

    its dependencies, track those instead Only: Composition SnapshotStateObserver NOT snapshotFlow Read observation: Derived state (with optimization)
  72. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) }
  73. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Foo readSet derivedValues
  74. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Foo readSet derivedValues derived readSet
  75. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Foo readSet derivedValues derived readSet state1 state2
  76. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Foo readSet derivedValues derived readSet state1 state2
  77. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Foo readSet state1 state2 derivedValues derived readSet state1 state2
  78. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Foo readSet state1 result state2 derivedValues derived readSet state1 state2
  79. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Foo readSet state1 result state2 result derivedValues derived readSet state1 state2
  80. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } derivedValues result Foo readSet state1 result state2 result derived readSet state1 state2
  81. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } derivedValues result 0 Foo readSet state1 result state2 result derived readSet state1 state2
  82. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } derivedValues result 0 Foo readSet state1 result state2 result
  83. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changed derivedValues result 0 Foo readSet state1 result state2 result
  84. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changed state1 derivedValues result 0 Foo readSet state1 result state2 result
  85. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changed state1 derivedValues result 0 Foo readSet state1 result state2 result
  86. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changed state1 state2 derivedValues result 0 Foo readSet state1 result state2 result
  87. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } changed state1 state2 derivedValues result 0 Foo readSet state1 result state2 result
  88. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } derivedValues result 0 Foo readSet state1 result state2 result changed state1 state2
  89. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } derivedValues result 0 Foo readSet state1 result state2 result changed state1 state2
  90. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } derivedValues result 0 Foo readSet state1 result state2 result changed state1 state2
  91. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } derivedValues result 0 Foo readSet state1 result state2 result changed state1 state2
  92. Dedup invalidations Read observation: Derived state (with optimization) val state1

    = mutableStateOf(0) val state2 = mutableStateOf(0) val result = derivedStateOf { state1.value + state2.value // 0 } @Composable fun Foo() { Text(result.value) } Snapshot.withMutableSnapshot { state1.value = -1 state2.value = 1 } derivedValues result 0 Foo readSet state1 result state2 result changed state1 state2 🥳
  93. @Composable fun Foo(items: List<Bar>) { val updatedItems = rememberUpdatedState(items) val

    sortedItems = remember { derivedStateOf { updatedItems.value.sorted() } } LazyColumn( Modifier.draw { sortedItems.value.forEach { drawDecoration(it) } } ) { items(sortedItems.value) { ... } } } Not executed until first used, in layout Then re-used in draw
  94. Selecting records message id value “he” “hello asdf” “hello ”

    written by 72 “hello world” color id value Red Blue written by 80 snapshot ID
  95. newest valid record “mutableState.value” get() = findRecord(SnapshotThreadLocal.get()) highest ID not

    higher than snapshot ID AND not in snapshot invalid set AND not tombstoned
  96. Cache derivedStateOf: Custom StateObject subclass. Calculation result and metadata are

    cached per-snapshot, in StateRecords. Uses snapshot IDs and write count to avoid recalculating result.
  97. Cache Algorithm: Have we ever calculated the result before? Is

    the result from this snapshot? Was anything changed in the snapshot? Have any dependencies been written to? Re-calculate result—is it different?
  98. Cache val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val

    derived = derivedStateOf { state1.value + state2.value } println(derived.value) Initializing snapshot ID 1 writeCount 0
  99. Cache snapshot ID 1 writeCount 0 state1 RECORD 1 value

    0 Initializing val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  100. Cache Initializing snapshot ID 1 writeCount 1 state1 RECORD 1

    value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  101. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    Initializing snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  102. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    Checking cached value snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) unset
  103. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    Calculating result (tracking dependencies) snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  104. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    state1 snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) Calculating result (tracking dependencies)
  105. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    state1 1 snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) Calculating result (tracking dependencies)
  106. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) Calculating result (tracking dependencies)
  107. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) Calculating result (tracking dependencies)
  108. Cache derived RECORD 1 value ∅ lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 Calculating result snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value // 0 } println(derived.value)
  109. Cache derived RECORD 1 value 0 lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 Calculating result snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value // 0 } println(derived.value)
  110. Cache derived RECORD 1 value 0 lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 Calculating result (advancing snapshot) snapshot ID 1 writeCount 2 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  111. Cache derived RECORD 1 value 0 lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 Calculating result (advancing snapshot) snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  112. Cache derived RECORD 1 value 0 lastSnapshotId ∅ lastWriteCount ∅

    state1 1 state2 1 Calculating result (updating metadata) snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  113. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Calculating result (updating metadata) snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  114. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Calculating result snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value)
  115. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Calculating result snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) // "0" Snapshot.withMutableSnapshot { println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) println(derived.value) }
  116. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Entering new snapshot snapshot ID 2 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } val state1 = mutableStateOf(0) val state2 = mutableStateOf(0) val derived = derivedStateOf { state1.value + state2.value } println(derived.value) // "0"
  117. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Entering new snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  118. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Checking cached value snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } set
  119. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 3 ≠ 2
  120. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Checking dependencies snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 1 = 1
  121. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Checking dependencies snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 1 = 1
  122. Cache derived RECORD 1 value 0 lastSnapshotId 2 lastWriteCount 0

    state1 1 state2 1 Updating cache metadata snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  123. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } Updating cache metadata
  124. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking dependencies snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  125. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking cached value snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } set
  126. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 3 = 3
  127. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 0 = 0
  128. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  129. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  130. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 RECORD ∅ value ∅ state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  131. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value ∅ state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  132. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  133. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  134. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  135. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD ∅ value ∅ Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  136. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value ∅ Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  137. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  138. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Writing to dependency snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  139. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking cached value snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } set
  140. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 3 = 3
  141. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking current snapshot snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 2 ≠ 0
  142. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Checking dependencies snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 3 ≠ 1
  143. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Recalculating result snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) } 0 = 0 -1 + 1 = 0
  144. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 1 state2 1 Updating cache metadata snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  145. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 3 state2 1 Updating cache metadata snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  146. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 3 state2 3 Updating cache metadata snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  147. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 3 state2 3 Advancing snapshot snapshot ID 3 writeCount 2 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  148. Cache derived RECORD 1 value 0 lastSnapshotId 3 lastWriteCount 0

    state1 3 state2 3 Advancing snapshot snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  149. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Updating cache metadata snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) state1.value = 42 println(derived.value) }
  150. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Updating cache metadata snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  151. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 0 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } state1 RECORD 1 value 0 RECORD 3 value -1
  152. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  153. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD ∅ value ∅ state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  154. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value ∅ state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  155. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  156. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Writing to dependency snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  157. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Checking cached value snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } set
  158. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Checking current snapshot snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 4 = 4
  159. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Checking current snapshot snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 1 ≠ 0
  160. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Checking dependencies snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 4 ≠ 3
  161. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 Recalculating result snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 43 ≠ 0 42 + 1 = 43
  162. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD ∅ value ∅ lastSnapshotId ∅ lastWriteCount ∅ state1 ∅ state2 ∅ Recalculating result (new cache record) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 42 + 1 = 43
  163. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value ∅ lastSnapshotId ∅ lastWriteCount ∅ state1 ∅ state2 ∅ Recalculating result (new cache record) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 42 + 1 = 43
  164. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value ∅ lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 ∅ Recalculating result (new cache record) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 42 + 1 = 43
  165. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value ∅ lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 3 Recalculating result (new cache record) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 42 + 1 = 43
  166. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 3 Recalculating result (new cache record) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) } 42 + 1 = 43
  167. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 3 Recalculating result (advancing snapshot) snapshot ID 4 writeCount 1 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  168. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 3 Recalculating result (advancing snapshot) snapshot ID 5 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  169. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId ∅ lastWriteCount ∅ state1 4 state2 3 Recalculating result (updating metadata) snapshot ID 5 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  170. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId 5 lastWriteCount 0 state1 4 state2 3 Recalculating result (updating metadata) snapshot ID 5 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) }
  171. Cache derived RECORD 1 value 0 lastSnapshotId 4 lastWriteCount 0

    state1 3 state2 3 RECORD 4 value 43 lastSnapshotId 5 lastWriteCount 0 state1 4 state2 3 Recalculating result snapshot ID 5 writeCount 0 state1 RECORD 1 value 0 RECORD 3 value -1 RECORD 4 value 42 state2 RECORD 1 value 0 RECORD 3 value 1 Snapshot.withMutableSnapshot { println(derived.value) // "0" println(derived.value) // "0" state1.value = -1 state2.value = 1 println(derived.value) // "0" state1.value = 42 println(derived.value) // "43" }
  172. Cache 5 cases: Reading for the first time in a

    snapshot, but no dependencies has been written to. at least one dependency has been written to. Reading multiple times in same snapshot, with no writes in-between. with at least one write in between. Nested derivedStateOf reads.
  173. Cache Calculation function must be pure. Only read snapshot states.

    Doesn't actually keep a list of dependencies, just stores a hash of (record, id) Actual value not tracked value's hashcode() is never called
  174. derivedStateOf Dedup invalidations Cache result • Coordinates with invalidation scopes.

    • Each scope determines for itself whether it needs to invalidate. • Uses caching when checking for changes. • Uses snapshot metadata to avoid recalculating result in most cases. • Uses StateRecords to keep cache isolated in different snapshots.
  175. Action Items: Open your codebase. Find at least one derivedStateOf

    that doesn't need to be there …or is used wrong. Remove it.
  176. Questions? Opening the Shutter on Snapshots: bit.ly/3x5zH53 Slides: bit.ly/45cqHYl [email protected]

    Snapshots blog series: bit.ly/3KAPprT androiddev.social/@zachklipp