When it comes to the `LaunchedEffect` side effect ...
# compose
t
When it comes to the
LaunchedEffect
side effect I usually see two patterns, Pattern 1:
Copy code
LaunchedEffect(state.value){
    doSomething()
}
Pattern 2:
Copy code
LaunchedEffect(state){
    snapshotFlow{ state.value }.collect{ doSomething() }
}
1. When do we prefer using one over the other? 2. Are there difference in the two patterns from the point of view of the
doSomething
function? 3. Would it be different if
doSomething
takes in
state.value
as argument for pattern 1, and
it
in pattern 2?
m
I guess that state is defined as
val
, so Pattern 1 is right.
t
It seems that they pretty much behave the same, its just that pattern 2 allows us to transform a compose's
State
into a
Flow
... Is this correct?
@m.c.shin. https://developer.android.com/develop/ui/compose/side-effects#snapshotFlow The official guide uses a
val
State
object as the key for
LaunchedEffect
I am curious about this too, whats wrong with just passing
Unit
when the state variable is
val
?
f
Unit if you only want it to fire once, state.value if you want it fire every time the state changes.
t
@Fergus Hewson in my example the body of the
LaunchedEffect
is followed immediately by a
snapshotFlow
, such that the
collect
block is fired every time the state changes
So if I understand correctly, the
snapshotFlow
is collected on "enter composition", and is alive until the parent composable exits composition
f
Yeah, you probably only want to collect this once. Unless there is something reason you want to re sub to the flow ... it is a new flow?
m
@Thomas, In android official guide case, It will be same between using key to listState and Unit.
👀 1
t
@Fergus Hewson I'm just investigating the behavior of side effect, you could imagine the
doSomething
function to be something like sending analytics information 🤔
(slighlty edited question for clarity)
I am really curious if there is difference in Pattern 1 and Pattern 2 when it comes to the behaviour of
doSomething
funciton invocations 👀 More specifically I wonder if there are "gotchas" to pattern 2
m
I think that both are almost same, but there has different that timing at doSomething() function called.
Pattern1 : state.value changed -> recomposing -> launchedEffect re-called -> doSomething() Pattern2 : state.value changed -> flow collected -> doSomething()
Considering timing, pattern2 seems to be better.
t
yeah that is how understand it would work too, but in both cases, isn't the
doSomething
task handled basically the same by the
CoroutineDispatcher
? If I am not mistaken, the
CoroutineDispatcher
of both the
LaunchedEffect
block and the
snapshotFlow
block is exactly the same as the one used by the
Recomposer
, therefore they should behave the same? 🤔
m
You are right about which CoroutineDispatcher it runs on. The only difference is the timing at doSomething() function is called.
Pattern1 case depends on recomposing, and Pattern2 is not.
t
Oh right! In pattern 2 the parent composable did not make any "read" attempts to the
state.value
, therefore the parent composable was never observing changes to the
state
. Because of this the parent don't necessarily have to recompose when
state.value
changes
b
The second pattern is much better from a performance perspective, pattern 1 you are both causing recomposition of the parent scope and reallocating and launching a new coroutine scope every time state changes, very expensive. Pattern 2 you launch once, and then just continue to observe. Much cheaper
As for the key, you should key with
state
rather than
Unit
because you need to relaunch if the actual
state
holder class changes (not the value in it), or else you would still be observing the old one
🌟 5
👍 4
t
In retrospect that was such a stupid question... I simply had to think in terms of recomposition scope and
remember
retainment lifetime to see the issues 🐵 Thanks again for all the answers!
v
@Thomas, two things: 1. no such thing as a stupid question. 2. I also relearned the recomposition lesson, so you asking this helped me too. 😊
plus1 1