Kyant
04/02/2024, 2:02 PMsnapshotFlow
can't detect the state changes within a frame, for example:
SideEffect {
// pos = 0 now
pos = player.currentPosition
doSomething()
pos = 0
}
I am tracking the position change of my player in Compose. If seek to next song, pos
(notice it may be 0 currently) will be set to player.currentPosition
(refresh the position), then it will be set as 0 immediately (for detecting position change and update notification internally).
The problem is: if pos
is 0 before seek to next song (= user haven't seek to another position), after the seeking the pos
is still 0, although it has an immediate value player.currentPosition
, and the snapshotFlow
can't detect the changes of pos
.jw
04/02/2024, 2:08 PMSnapshot.withMutableSnapshot
it should be observedKyant
04/02/2024, 2:11 PMset
is done in another project that doesn't use Compose. I am using interface and interface delegation to inject states to it.jw
04/02/2024, 2:14 PMKyant
04/02/2024, 2:16 PMobject AudioPlayerState : PlayerState {
override var currentPosition: Long by mutableLongConfigStateOf(0L)
}
class MediaLibraryService : BaseMediaLibraryService(
playerState = AudioPlayerState,
)
jw
04/02/2024, 2:17 PMprivate val realState = mutableLongConfigStateOf(0L)
override var currentPosition: Long
get() = realState.value
set(value) { Snapshot.withMutableSnapshot { realState.value = value } }
Kyant
04/02/2024, 2:20 PMKyant
04/02/2024, 2:35 PMpositionIncrement
, but it's not elegantStylianos Gakis
04/02/2024, 2:39 PMSnapshot.withMutableSnapshot
that in-between doing those two, if there is not a frame clock tick, there will not be a new composition, therefore the snapshotFlow will also not get that new emission?registerApplyObserver
internally to read for state changes, but does that get called before there is a frame clock tick too? The docs for it say “Register an apply listener that is called back when snapshots are applied to the global state.“. I don’t think I know enough to confidently answer this though.registerApplyObserver
is supposed to read those regardless of a tick then ignore what I am saying.jw
04/02/2024, 2:40 PMjw
04/02/2024, 2:40 PMStylianos Gakis
04/02/2024, 2:44 PMwithMutableSnapshot
, but adding it should make the snapshotFlow
really get all the emissions, since they will be individually committed to the global snapshot.Kyant
04/02/2024, 2:44 PMjw
04/02/2024, 2:45 PMKyant
04/02/2024, 2:46 PMAlex Vanyo
04/02/2024, 3:51 PMsnapshotFlow
will emit every incremental state update - it can internally have a collectLatest
-type behavior for state updatesAlex Vanyo
04/02/2024, 7:21 PM@Preview
@Composable
fun Repro() {
var state by remember { mutableStateOf(0) }
val receivedStates = remember { mutableStateListOf<Int>() }
LaunchedEffect(Unit) {
snapshotFlow { state }
.onEach {
receivedStates.add(it)
}
.collect()
}
Column {
Text("receivedStates: ${receivedStates.toList()}")
Button(
onClick = {
Snapshot.withMutableSnapshot {
state++
}
Snapshot.withMutableSnapshot {
state++
}
}
) {
Text("+2")
}
}
}
This ends up only addding even numbers to the receivedStates
listZach Klippenstein (he/him) [MOD]
04/02/2024, 7:48 PMjw
04/02/2024, 7:50 PMjw
04/02/2024, 7:51 PMZach Klippenstein (he/him) [MOD]
04/02/2024, 7:54 PMZach Klippenstein (he/him) [MOD]
04/02/2024, 7:55 PMZach Klippenstein (he/him) [MOD]
04/02/2024, 7:56 PMyschimke
04/02/2024, 8:14 PMyschimke
04/02/2024, 8:15 PMOliver.O
04/03/2024, 7:00 PMsnapshotFlow
and the Snapshot
system in general, I've always understood it to behave that way.
What was less clear, and really became clearer after re-reading the latest version of the guide's section on Mutating the UI state from background threads, is that MutableState
was never really thread-safe, which I had assumed it to be because of the snapshot system. I could verify that this can make Compose sporadically miss an update, which I could only observe with intense parallelism.
I'd find it very valuable to explicitly hint at using Snapshot.withMutableSnapshot
whenever writers can be on a non-UI thread in the docs of mutableStateOf
, MutableState
, and the like.Stylianos Gakis
04/03/2024, 7:28 PMSnapshot.withMutableSnapshot
?Oliver.O
04/03/2024, 8:35 PMSnapshot.withMutableSnapshot
block, will get conflated.
2. Jack and Alex dropped Snapshot.withMutableSnapshot
, and Zach referred to state changes from other threads.
Reading about aspect 2 made me check my scenario, leading to the observation I posted. If everything is confined to the main dispatcher as the above examples suggest, withMutableSnapshot
is not needed (and wouldn't help to get more updates). But as soon as other threads may change mutable state, is is essential to use withMutableSnapshot
in oder to avoid subtle bugs. This could be documented more prominently. So not the original issue, but related to the follow-up discussion.Oliver.O
04/04/2024, 9:02 AMWarning: Updating Compose state from a non UI thread without usingThis is generic wording, which I understand to include a singlemay cause inconsistencies in the state produced.Snapshot.withMutableSnapshot{ }
MutableState
update from a non-UI thread.
I was observing Compose very sporadically missing a single update to a MutableState<String>
in a single-writer, non-UI thread scenario under extreme load. The problem went away when surrounding the update with Snapshot.withMutableSnapshot
. Is that the expected behavior?Zach Klippenstein (he/him) [MOD]
04/04/2024, 3:27 PM