Slide 1

Slide 1 text

Zach Klippenstein (he/him) Google Opening the shutter on snapshots

Slide 2

Slide 2 text

@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

Slide 3

Slide 3 text

@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

Slide 4

Slide 4 text

@zachklipp What for? What do they do? How do they work? What can we do with them?

Slide 5

Slide 5 text

@zachklipp

Slide 6

Slide 6 text

@zachklipp

Slide 7

Slide 7 text

@zachklipp

Slide 8

Slide 8 text

Snapshots are Git for your variables @zachklipp

Slide 9

Slide 9 text

Composition is the release manager @zachklipp

Slide 10

Slide 10 text

@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

Slide 11

Slide 11 text

@zachklipp ACID “In-memory”

Slide 12

Slide 12 text

@zachklipp ACID tomicity On-disk “a”, Red “b”, Red “b”, Blue “b”, Blue

Slide 13

Slide 13 text

@zachklipp A CID onsistency On-disk “a” “b” “c” “c” “c” tomicity

Slide 14

Slide 14 text

@zachklipp AC ID solation On-disk “a”, Red “a”, Blue “b”, Red “b”, Blue onsistency urability

Slide 15

Slide 15 text

@zachklipp ACI D urability On-disk solation

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

@zachklipp Database Snapshots ACID ACID transactional snapshots commit/rollback apply/dispose views derived state

Slide 19

Slide 19 text

@zachklipp How do they work?

Slide 20

Slide 20 text

@zachklipp

Slide 21

Slide 21 text

@zachklipp

Slide 22

Slide 22 text

No magic @zachklipp

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

No compiler plugin @zachklipp

Slide 25

Slide 25 text

@zachklipp (but shipped with it) Snapshots ≠ Compose

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

@zachklipp snapshots ThreadLocal integers linked lists

Slide 28

Slide 28 text

@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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@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”

Slide 35

Slide 35 text

@zachklipp Snapshots API State Objects takeNestedSnapshot takeNestedMutableSnapshot withMutableSnapshot registerApplyObserver val state = mutableStateOf(“”) 
 mutableStateListOf mutableStateMapOf …whatever!

Slide 36

Slide 36 text

@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

Slide 37

Slide 37 text

@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

Slide 38

Slide 38 text

@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

Slide 39

Slide 39 text

@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

Slide 40

Slide 40 text

@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

Slide 41

Slide 41 text

@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

Slide 42

Slide 42 text

@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

Slide 43

Slide 43 text

@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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

@zachklipp Git: commit SHAs

Slide 46

Slide 46 text

@zachklipp Snapshot IDs

Slide 47

Slide 47 text

@zachklipp Walkthrough: create child, apply val message = mutableStateOf("") val snapshot = Snapshot.takeMutableSnapshot() snapshot.enter { message.value = "hello" } snapshot.apply() message

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

@zachklipp Snapshot rules: Create child Apply Advance

Slide 68

Slide 68 text

@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

Slide 69

Slide 69 text

@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()

Slide 70

Slide 70 text

@zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("") val color = mutableStateOf(Red) message color global snapshot

Slide 71

Slide 71 text

@zachklipp Walkthrough: create 2 children, apply val message = mutableStateOf("") val color = mutableStateOf(Red) message “” color Red global snapshot

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

@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()

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

@zachklipp MutableState message “” “hello” interface MutableState : State { override var value: T operator fun component1(): T operator fun component2(): (T) -> Unit }

Slide 142

Slide 142 text

@zachklipp SnapshotMutableState MutableState message “” “hello” interface SnapshotMutableState : MutableState { val policy: SnapshotMutationPolicy } interface MutableState : State {

Slide 143

Slide 143 text

@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 {

Slide 144

Slide 144 text

@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 {

Slide 145

Slide 145 text

@zachklipp message “” “hello” interface StateObject { value: T, override val policy: SnapshotMutationPolicy ) : StateObject, SnapshotMutableState { override var value: T get() = TODO("???") set(value) { TODO("???") } } SnapshotMutableStateImpl StateObject :

Slide 146

Slide 146 text

@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 :

Slide 147

Slide 147 text

@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("???") } }

Slide 148

Slide 148 text

@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("???") } } }

Slide 149

Slide 149 text

@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("???") } } }

Slide 150

Slide 150 text

@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 } } }

Slide 151

Slide 151 text

@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 } } }

Slide 152

Slide 152 text

@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 } } }

Slide 153

Slide 153 text

@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 } } }

Slide 154

Slide 154 text

@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 } } }

Slide 155

Slide 155 text

@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 } } }

Slide 156

Slide 156 text

@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 } } }

Slide 157

Slide 157 text

@zachklipp internal class SnapshotMutableStateImpl( val firstStateRecord: StateRecord fun prependStateRecord(value: StateRecord) fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? = null }

Slide 158

Slide 158 text

@zachklipp internal class SnapshotMutableStateImpl( value: T, override val policy: SnapshotMutationPolicy ) : StateObject, SnapshotMutableState { override fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { } }

Slide 159

Slide 159 text

@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 } } } }

Slide 160

Slide 160 text

@zachklipp What can we do with them?

Slide 161

Slide 161 text

@zachklipp Custom state objects class Range { var start: Int = 0 var end: Int = 0 }

Slide 162

Slide 162 text

@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 } } }

Slide 163

Slide 163 text

@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 } } }

Slide 164

Slide 164 text

@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 } } }

Slide 165

Slide 165 text

@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 } } }

Slide 166

Slide 166 text

@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 } } }

Slide 167

Slide 167 text

@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 } } }

Slide 168

Slide 168 text

@zachklipp Custom state objects class Range : StateObject { }

Slide 169

Slide 169 text

@zachklipp Custom state objects class Range : StateObject { override fun mergeRecords( previous: StateRecord, current: StateRecord, applied: StateRecord ): StateRecord? { } }

Slide 170

Slide 170 text

@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 } } }

Slide 171

Slide 171 text

@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 } } }

Slide 172

Slide 172 text

@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 } } }

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

@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]) } } } }

Slide 176

Slide 176 text

@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]) } } } }

Slide 177

Slide 177 text

@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]) } } } }

Slide 178

Slide 178 text

@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]) } } } }

Slide 179

Slide 179 text

@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]) } } } }

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

@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

Slide 185

Slide 185 text

@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 }

Slide 186

Slide 186 text

@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 }

Slide 187

Slide 187 text

@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 }

Slide 188

Slide 188 text

@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 }

Slide 189

Slide 189 text

@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() } } } }

Slide 190

Slide 190 text

@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() } } } }

Slide 191

Slide 191 text

@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() } } } }

Slide 192

Slide 192 text

@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() } } } }

Slide 193

Slide 193 text

@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() } } } }

Slide 194

Slide 194 text

@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() } } } }

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

Snapshots are Git for your variables @zachklipp

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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