Hi, does it make any sense to try to implement sth...
# compose
l
Hi, does it make any sense to try to implement sth like:
Copy code
@Composable
fun <T> delayedStateOf(init: T, delayMs: Long = 200, calculation: () -> T): State<T> {
    ....
    val derived = derivedStateOf { if(someCacheNotOutdated) useCache else calculation()... } // FIXME: correct conditional control of reads (subscriptions)
    ....
    return derived
}
So this
delayedStateOf
would be a bit lazy updating, so I can use it as input for some low priority part of UI. Maybe better idea would be to use
SnapshotStateObserver
directly - I experimented with it a bit, but I have other issues with that Or maybe only reasonable solution is to go even lower level and play with snapshots directly? (like
snapshotFlow
does)
BTW my current best working idea is to just use
sapshotFlow
and
debounce
the flow to produce new state, but I think snapshotFlow+debounce is a bit heavy and gives me little control of caching strategy
Copy code
@OptIn(FlowPreview::class)
@Composable fun <T> debouncedStateOf(init: T, delayMs: Long = 200, calculation: () -> T): State<T> = produceState(init) {
    snapshotFlow(calculation).debounce(delayMs).collect { value = it }
}
z
If you don’t want to deal with flow, you can just use
snapshotFlow.collectLatest
and put whatever delay and caching logic you want in there, without dealing with flow operators.
l
@Zach Klippenstein (he/him) [MOD] Right, the
collectLatest
is more flexible. Thanks. But still my bigger issue is that the
snapshotFlow
will eagerly recalculate on any state change without any delay no matter how I collect it. Also it seems to me that
snapshotFlow
implementation is a overkill for second reason (if I'm reading it's implementation correctly): it registers new observer called on any global apply on any snapshot (even totally unrelated to my code). How would you approach implementing own "lazy"/"delayed" state reading? Would you try something similar to
snapshotFlow
implementation? Or would you try to use
SnapshotStateObserver
? Also (I promise it's last question 😉 ) Is compose team still planning to introduce low-priority/high-priority compositions? I think I heard of such plans somewhere. Ideally I would like to just wrap part of my UI in some
LowPriority {...}
composable fun.
z
No matter how you do it, the only way to observe changes to snapshot state is to register a global listener. You can listen to reads and writes inside a particular non-global snapshot when you create the snapshot, but if there’s any chance any of the state objects you care about could ever be changed outside of that snapshot, that will not work at best and more likely allow future bugs to sneak in.
If you want to observe changes without recalculating, using the
SnapshotStateObserver
api is a good approach.
s
If I read this correctly, you want
derivedStateOf
, but never trigger calculation if delay haven't passed I think you can copy
DerivedState
and modify
ResultRecord
to keep the time of its init Then you can update
isValid
here: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]ndroidx/compose/runtime/DerivedState.kt;l=90?q=derivedStateOf to return true early if delay hasn't passed yet. There's a million reasons not to do it though, as it is a bit dodgy from Stable guarantees (e.g. state might change but you won't know until the next read/write), but it should work in certain circumstances. Just make sure your UI knows to update itself every few ms to re-read the state 😛
Also also, about
LowPriority
, we are thinking about something close to it, but it certainly won't happen for a release cycle or two until we figure out the use cases and requirements.
l
Generally I wanted one less important part of my UI (that display some logs) to never slow down other more important part (where I run some visual tests); And yes: I thought the kind of
derivedStateOf
that never recalculate more often than some defined delayMs=200 would do it. But I also never want it to skip invalidation totally, but just delay it a bit (where ignoring some in between changes is fine because it's always eventually consistent) - so not sure if that is violation of Stable guarantees. Thanks for pointing out what to check in DerivedState.kt. Maybe I'll dive into it when I find more time, but I don't want to shoot myself in the foot, so maybe I don't need this feature so much 😅 Maybe something like custom Composer/Composition/Window/.. with slower but consistent invalidations, would be less hacky approach to LowPriority UI problem.