https://kotlinlang.org logo
Title
c

Chris Johnson

03/22/2022, 10:45 PM
:android-wave: Is there a way to determine when a key has changed and when that is true run certain blocks of code in a composable? For example, I want to run a certain block of code only when
scrollState.isScrollInProgress
changes from false to true or true to false. I don't want to use a LaunchedEffect with a key to avoid creating a new CoroutineScope everytime someone starts/stops scrolling.
f

Filip Wiesner

03/23/2022, 5:35 AM
DisposableEffect
has a key parameters and does not create a Coroutine scope
👍 1
c

Chris Johnson

03/23/2022, 6:05 AM
True! I was hoping there would be some kind of mechanism that didn't involve a side effect but maybe this use case is too fringe to warrant something like that. That's definitely the lesser of the two evils, ty 🙂
z

Zach Klippenstein (he/him) [MOD]

03/23/2022, 6:14 PM
The main problem with something like
LaunchedEffect(scrollState.isScrollInProgress)
is not that you’re creating a coroutine for every time scroll starts/stops – that’s perfectly find (the entire animation system works that way, including scroll flings). The problem is that it requires recomposition unnecessarily. I think this is what you’re looking for:
LaunchedEffect(scrollState, …) {
  snapshotFlow { scrollState.isScrollInProgress }
    .collect { isScrolling ->
      …
    }
}
👌 1
☝️ 1
c

Chris Johnson

03/23/2022, 6:17 PM
That was actually what I ended up doing. And just to make sure, the reason it's recomposing unnecessarily is because it's needing to read the value of
scrollState.isScrollInProgress
over and over?
Edit: Nvm, the recomposition is on the LaunchedEffect, I see.
z

Zach Klippenstein (he/him) [MOD]

03/23/2022, 8:27 PM
Yea, if you’re reading
isScrollInProgress
in the composable in order to pass it to
LaunchedEffect
as a key, then that’s going to recompose every time that state changes.
👍 1
c

Chris Johnson

03/23/2022, 8:30 PM
Hey thanks for responding to so many people's questions. In case no ones thanked you recently it really helps 🙂
1
z

Zach Klippenstein (he/him) [MOD]

03/23/2022, 8:43 PM
It was a nice break from my regular job this morning 😛
c

Chris Johnson

03/23/2022, 9:05 PM
I can only imagine 😨
j

james

03/23/2022, 10:43 PM
totally agree with that “thanks” message! between Zach and Jossi, I think I’ve been able to avoid many mistakes in learning Compose very much appreciated! 💙
I had this answer bookmarked for myself to come back to when I had a chance to compare to some of my code, and I had a further question on this, if I may bug you Zach? I am currently achieving the same thing with the following code, which works as intended:
val isScrolling by remember {
    derivedStateOf { scrollState.isScrollInProgress }
}
if (isScrolling) {
    LaunchedEffect(isScrolling) {
        // ...
    }
}
is this an okay practice? or should I be changing my code to something like the snapshotFlow example you posted above?
m

myanmarking

04/05/2022, 11:47 AM
I dont umderstand how both solution works. Both snapshot and derivedState work on State<>, but scrollInProgress is just a regulat field.
a

Albert Chang

04/05/2022, 1:58 PM
I don’t think there’s any point in using
derivedStateOf
here as
remember { derivedStateOf { scrollState.isScrollInProgress } }
is the same as
scrollState.isScrollInProgress
if
scrollState
doesn’t change (which is likely the case). @myanmarking
scrollState.isScrollInProgress
comes from a snapshot state and will trigger recomposition.
m

myanmarking

04/05/2022, 9:53 PM
Will trigger on scrollstate change or on the boolean itself?
In that case, derivedstate scrollinprogress might make sense then?
a

Albert Chang

04/06/2022, 1:24 AM
On both cases. However,
scrollState
is unlikely to change as it is normally created by
rememberScrollState()
and in that case
derivedStateOf
makes no point.
z

Zach Klippenstein (he/him) [MOD]

04/14/2022, 7:02 PM
There’s no reason to use
derivedStateOf
simply to read the value of
isScrollInProgress
, since the rate of change is the same. Where it makes sense to use
derivedStateOf
with scrolling is, for example, if you need to calculate some condition based on the scroll value. In that case, the rate of change of the scroll state is very frequent – every frame during a scroll/fling – but the rate of change of the derived state is much lower. E.g.
val isNearTop by remember(scrollState) { derivedStateOf { scrollState.value < 10 } }
Re: using a
snapshotFlow
vs keying a
LaunchedEffect
off the changing value, the latter requires more unnecessary recomposition than the former. If the rate of change is low, as it is for
isScrollInProgress
, that’s a negligible cost. But it’s also just more moving parts, and imo less elegant. When the value changes, you trigger a recomposition, that starts a new effect, which starts a new coroutine, which will get torn down when the value changes again. Vs with
snapshotFlow
, you start one coroutine, it runs continuously, and the only thing that happens when the state changes is a new flow emission. I’m guessing the
snapshotFlow
approach will also scale better if your logic grows in complexity.
j

james

04/19/2022, 12:44 AM
excellent, thanks so much for the follow up explanation 🙏