langara
10/24/2022, 2:38 PM@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)langara
10/24/2022, 2:47 PMsapshotFlow
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
@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 }
}
Zach Klippenstein (he/him) [MOD]
10/24/2022, 3:33 PMsnapshotFlow.collectLatest
and put whatever delay and caching logic you want in there, without dealing with flow operators.langara
10/24/2022, 7:01 PMcollectLatest
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.Zach Klippenstein (he/him) [MOD]
10/24/2022, 7:07 PMZach Klippenstein (he/him) [MOD]
10/24/2022, 7:08 PMSnapshotStateObserver
api is a good approach.shikasd
10/24/2022, 8:30 PMderivedStateOf
, 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 😛shikasd
10/24/2022, 8:31 PMLowPriority
, 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.langara
10/24/2022, 10:28 PMderivedStateOf
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.