Yan Pujante
03/20/2021, 5:03 PMclass Model {
var value1 by mutableStateOf(0)
private set
var value2 = 0
private set
fun onClick1() { value1++ }
fun onClick2() { value2++ }
}
val model = Model()
@Composable
fun TestModel() {
val m = Modifier.padding(10.dp)
Column {
Row { Text(model.value1.toString(), modifier = m); Button(onClick = {model.onClick1()}) { Text("Val1++")} }
Row { Text(model.value2.toString(), modifier = m); Button(onClick = {model.onClick2()}) { Text("Val2++")} }
}
}
which produces the result in the attached screenshot.
I have a few questions:
1) Is it valid to have the state in an external object (like) Model (as opposed to have it directly inside the composable function)? Are there caveats?
2) I don't understand how Compose recognizes that value1 has changed since its underlying representation is a State but it is exposed as a simple value. How does compose know this? How is value2 different from value1 from a user point of view (IntelliJ tells me that value1 is public final var value1: Int when using Ctrl-Q)?
3) if I understand correctly (some of) the magic of compose comes from the compiler. Is there a way to "see" this magic in action? Like a flag? (something like compiling .cpp to .s to "see" the assembly code)Adam Powell
03/20/2021, 5:06 PM@Composable
fun MyComposable(
state: MyState = remember { MyState() }Adam Powell
03/20/2021, 5:08 PMSnapshot API for more info on it.Adam Powell
03/20/2021, 5:09 PMAdam Powell
03/20/2021, 5:10 PMsnapshotFlow {} API is a high-level way to see all of the machinery in action; you can put any code you want into the snapshotFlow block, and when you .collect the flow, it will emit a new value returned by that block whenever any of the snapshot state that was read inside the block changes.Adam Powell
03/20/2021, 5:12 PMsnapshotFlow to observe changes to your own snapshot-backed objects outside of any sort of compose-related context. If you were to write
snapshotFlow { model.value1 }
.collect { println("new value: $it" }
you would be able to see the values changingYan Pujante
03/20/2021, 5:14 PMAdam Powell
03/20/2021, 5:18 PMSnapshot.takeSnapshot and then the call to enter that runs the expression blockYan Pujante
03/20/2021, 5:18 PMmutableStateOf call must somehow registers itself somewhere otherwise there is no way that an opaque block of code could be analyzed by snapshotFlow to determine what is going on...Adam Powell
03/20/2021, 5:21 PMSnapshot.enter sets up threadlocals for the current snapshot and that forms the communication pathAdam Powell
03/20/2021, 5:22 PMYan Pujante
03/20/2021, 5:23 PMmutableStateOf or (flow.collectAsState) is required for the system to workAdam Powell
03/20/2021, 5:24 PMAdam Powell
03/20/2021, 5:25 PMFlow.collectAsState just uses a LaunchedEffect to collect the flow and write into a mutableStateOf object, so it's the same thing)Yan Pujante
03/20/2021, 5:29 PMYan Pujante
03/20/2021, 5:29 PMAdam Powell
03/20/2021, 6:21 PMAdam Powell
03/20/2021, 6:22 PMYan Pujante
03/20/2021, 6:34 PMmutableStateOf does and the compose/snapshot system. The proof being that if you just declare a "regular" variable it doesn't work. I really thought that the compiler was doing some kind of trick (I have seen in some places reference to the compose compiler, so the compose compiler is doing something) but I guess I was wrong. At the end of the day I understand it's not magic 😉 and I would like to see some paper or presentation, describing how when I write mutableStateOf somehow it "notifies" (probably not the right word) that whenever a snapshot is taken then this "state" needs to be part of it (ThreadLocal? Global Objects?). There IS a side effect happening when I call mutableStateOf that reaches OUTSIDE of my class and that is the magic that scares me (scares is probably too strong of a word 😉) . Yes I can look at the source code but a high level explanation/architecture would be super useful (at least to me).Adam Powell
03/20/2021, 6:41 PMget or set is called on SnapshotMutableState.valueChuck Jazdzewski [G]
03/22/2021, 6:40 PMmutableStateOf. This instance is observable (https://en.wikipedia.org/wiki/Observer_pattern). Whenever the object's value property is written to the snapshot mechanism will inform any listeners that the object was changed (through a global snapshot apply observer). Composition is one such observer (as is layout and draw).
During composition, a snapshot is created with a read observer which will be informed whenever a read of a observable object is performed (even transitively). It records an association with the part of the composition that is being either produced or updated (called a recompose scope) and when it is informed later that the object was changed it will schedule recompose scope to be recomposed.
In the above example, reading value1 in TestModel will cause TestModel to recompose whenever value1 changes. Incrementing value1 causes an apply notification to be sent to the composer which will invalidate TestModel and schedule a recompose to happen in the next choreographer frame. TestModel is re-invoked by the composer and the new value is updated in the Text . Changing value2 updates the value but does not schedule a recompose as nothing tells the composer that it has changed.