DEV Community

Discussion on: Introduction to the Compose Snapshot system

Collapse
 
gmk57 profile image
gmk57

@zachklipp Could you please clarify a bit on this point? If snapshot system has these atomicity guarantees, we can replace StateFlow<SomeDataClass>.update { it.copy(...) } with several separate States, greatly simplifying ViewModel code without sacrificing behavior correctness.

BTW, will upcoming parallel (multi-threaded) composition change anything in this regard?

Collapse
 
zachklipp profile image
Zach Klippenstein

Generally, yes. It can often be a lot more natural and simpler to express multiple parts of state as separate properties vs having to wrap them all up into one immutable thing to shove inside a MutableStateFlow.

Note that there are some subtle differences since update handles contention by simply re-running the function, whereas the snapshot system can sometimes resolve conflicts without re-running anything by merging conflicting values in a type-aware way. That said, in mobile apps contention is actually probably quite rare.

Multi-threaded composition will probably have lots of interesting implications, but thread safety is thread safety – it shouldn't require any significant changes to how snapshots work.

Thread Thread
 
gmk57 profile image
gmk57

Thanks for the detailed reply and for the warning about contention. It may be rare, but that means better chances of not catching it in tests and then having a rare crash with SnapshotApplyConflictException in production. ;)

So, to recap:

  • For atomic updates of single value (e.g. counter increments) from background thread we should use conflict-free data types and custom conflict resolver (SnapshotMutationPolicy).
  • To update atomically multiple related parts of state from background thread we should take an explicit snapshot and retry until success (like MutableStateFlow.update() does), for example:
fun <R> withSnapshotRetrying(block: () -> R): R {
    while (true) {
        try {
            return Snapshot.withMutableSnapshot(block)
        } catch (e: Exception) {
            if (e !is SnapshotApplyConflictException) throw e
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • But for doing the same things from the main thread we don't need an explicit snapshot, the changes will be atomic anyway (because global snapshot advancing happens on the main thread).
  • In case of multi-threaded composition all of the above remains true (Composables won't see any partial updates), because Compose wraps compositions in snapshots.

Correct?