Regarding `Snapshot.takeMutableSnapshot()`, when i...
# compose
g
Regarding
Snapshot.takeMutableSnapshot()
, when inside a
readObserver
callback, how do you subscribe a composable to reads of the supplied state instance?
z
AFAIK the only way to “subscribe” a composable to a state object is to read it in the composable. Can you elaborate about what you’re trying to do?
g
I’m trying to figure out how to make use of this Api. The params description for
readObserver
states: “Composition, layout and draw use readObserver to implicitly subscribe to changes to state objects to know when to update”. This suggests to me that
readObserver
serves as a place where you subscribe a composable to the
State
object provided by the callback parameter… Why would you otherwise care about
State
read? I mean you can’t know (read)
State.value
inside the callback, that will lead to infinite recursion so what else can you do with the
State
instance ?
z
I’m trying to figure out how to make use of this Api
What are you trying to do with the API though? These APIs are very low-level you shouldn’t need to use them unless you’re building your own Compose-like framework.
I mean you can’t know (read) 
State.value
 inside the callback
What callback? Again, not sure what you’re trying to do. Are you trying to build some UI? Build a framework?
g
What are you trying to do with the API though? These APIs are very low-level you shouldn’t need to use them unless you’re building your own Compose-like framework.
I'm not trying to build anything at this point. I'm trying to understand what state management facilities are at my disposal. BTW. I Love low-level 🙂
What callback?
The
readObserver
callback.
Example:
Copy code
val state = mutableStateOf(0)

val readObserver: (Any) -> Unit = { observedState ->

    // So I know some state was read, so what ?

    if (observedState == state)  {
        // Ok, I can tell it's some specific state...
        // But I can't tell what value it stores... Uncomment to see why.
        // println("state.value: ${state.value}")
        println("state read")

        // So what do you do with an 'observedState' ?
        // The docs suggest you can register it with a composable, but how ??
    }
}

val snapshot = Snapshot.takeMutableSnapshot(readObserver)

snapshot.enter {
    println("${state.value}")
}
snapshot.dispose()
z
Ah, I see. Really the only thing you can do with the information that a particular state was read is to keep track of it for later, to check for in a write observer. If your write observer is notified that some state value changed, you can check if that state was previously read and take some action. Eg compose will take the action of invalidating the recompose scopes that read the value.
There’s no way to “register” states with composables arbitrarily.
The “composable” machinery in compose uses read observers to peek into which state values each composable reads. So any time a composable is executed, it’s actually executed inside a read observer that is registered by the compose runtime.
g
So any time a composable is executed, it’s actually executed inside a read observer that is registered by the compose runtime.
What do you mean by "read observer" ? The snapshot.enter scope is where code executes and the scope observes both reads and writes... the read/write observers above are notification callbacks functions observed code doesn't run in these callbacks.
z
A simple example is the implementation of
snapshotFlow
- take a look at that. The callbacks merely notify you that the code running in the context of the snapshot read or wrote some state value.
So if you have a composable that looks like this:
Copy code
@Composable fun Display(value: State<*>) {
  Text(value.value.toString())
}
Then when the runtime actually runs that function, it does something like this pseudo code:
Copy code
val readStates = mutableSetOf<*>()

observeReads(
  onRead = { readStates += it },
  codeToObserve = { Display(value) }
)

addGlobalWriteObserver(
  onWrite = { writtenState ->
    if (writtenState in readStates) {
      scheduleRecomposition()
    }
  }
)
Those aren’t the actual APIs or code but conceptually that’s basically how the observe logic works.
observeReads
executes
codeToObserve
immediately and invokes
onRead
any time
codeToObserve
reads a state value. Then, any time any state value is written anywhere in the process, we check if it was one of the values read by
codeToObserve
and, if it was, schedule a recomposition to eventually run and process the new state value.
g
Fantastic! The snapshotFlow example is exactly what I was looking for to see how the Api is actually used. Are you familiar with a sample similar to that which uses writeObserver (just to see if there any special nuances there) ? The pseudocode of your observeReads actually looks like another Api I'm investigating called SnapshotStateObserver, is this the actual Api used to call composable ?
z
SSO is a higher level api that basically implements this pattern for you. I can’t remember if it is used by the compose runtime for composables, but you can do a find usages for that class name or just search for it on cs.android.com to find out.
Also idk if you’ve seen this blog post but it talks about the same stuff: https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn