https://kotlinlang.org logo
#compose
Title
# compose
d

Dominaezzz

02/25/2021, 10:50 PM
I think
produceState
should forget the initial value if any of the input keys change.
Especially since it's used to implement
collectAsState
for `StateFlow`s.
l

Leland Richardson [G]

02/25/2021, 11:20 PM
can you file a bug for this? I’m especially curious if you have a use case / example where this was tripping you up. I’m inclined to agree with you about this but it’s always nice to have a non-contrived example
z

Zach Klippenstein (he/him) [MOD]

02/26/2021, 12:13 AM
Right now, if the input keys change, is the initial value set on the state again?
l

Leland Richardson [G]

02/26/2021, 12:55 AM
no
i think that’s the behavior that makes the most sense though. wdyt?
z

Zach Klippenstein (he/him) [MOD]

02/26/2021, 12:58 AM
Oh, I thought from @Dominaezzz’s question that the desired behavior was to not reset to the initial value when the keys change.
If the initial value changes along with the keys, e.g. in the
collectAsState
case where the flow itself is the key, and the initial value is derived from the flow, then it makes sense. But it’s possible there could be a case where the initial value is not derived from the keys, in which case it might be weird to change the value back to the original initial value instead of just leaving the last state there. I’m not sure which case is more general.
l

Leland Richardson [G]

02/26/2021, 1:09 AM
yeah i agree which is why i was curious to get the real world use case to help inform the decision
a tangentially related but different bug: https://issuetracker.google.com/180450267
s

Sean McQuillan [G]

02/26/2021, 1:14 AM
Also curious (subscribing). Original idea was that the initial value was "more friendly values of null prior to first production" but it'd be great to see how that's not working
a

Adam Powell

02/26/2021, 2:39 AM
I'm inclined to consider the behavior of
produceState
and
StateFlow.collectAsState
separately. Snapping to the current
StateFlow.value
for a new StateFlow seems more compelling than changing
produceState
both behaviors have their uses but the requested behavior of resetting to initial can be obtained from the current API by wrapping in
key(...) {}
- doing the reverse if
produceState
were changed would be more cumbersome
(though admittedly the key wrapper gives you a different State instance altogether, which can carry its own issues)
in any case, this is the second time someone has asked for this in the past month or so
d

Dominaezzz

02/26/2021, 9:39 AM
I'll try and write up my specific use cases.
So I filed a bug for this https://issuetracker.google.com/issues/181308257 . I got somewhat impatient while writing as it's pretty late where I am. I'll add some more stuff tomorrow once I recharge from work.
🙏🏽 1
1
🙏 1
z

Zach Klippenstein (he/him) [MOD]

02/26/2021, 9:15 PM
i wish i wrote bug reports that thorough on an empty tank lol. ’d!
s

Sean McQuillan [G]

02/26/2021, 9:29 PM
@Adam Powell it's cheaper to just make
value = Initial
as the first line of the producer lambda, as it will restart when the keys change
a

Adam Powell

02/26/2021, 9:59 PM
oh, yeah good call.
It doesn't make the first recomposition with the new key though, it happens after the coroutine dispatch after composition apply
overall, I kind of want to pull
produceState
from the API entirely and observe
d

Dominaezzz

03/07/2021, 3:34 PM
It doesn't make the first recomposition with the new key though
What does this mean? If you set a mutable state during (re?)composition, do you still see the old value until the next recomposition?
a

Adam Powell

03/07/2021, 3:46 PM
The body of a
produceState {}
doesn't run during composition, it runs after the composition is applied. The rest of that same composition pass will have seen the previous value. The change will schedule another recomposition for a future frame, not affect the recomposition that already happened.
Same for all Effect functions
If a snapshot state object is set during recomposition, it behaves like a plain old object - anything that read the old value before it was changed sees the old value, and anything that read the new value after the change sees the new value.
d

Dominaezzz

03/07/2021, 3:50 PM
Ahhhhh I see where I misunderstood. That makes sense.
👍 1
Does setting a snapshot state object before it's read, like doing
value = Initial
as Sean suggested, count as side effect? Specifically, the kind of side effect we're supposed to avoid in composable functions.
"before it's read" -> "before it's read in that composition"
Nvm, I think I just answered my own question lol.
a

Adam Powell

03/07/2021, 4:00 PM
Setting a snapshot object in composition before reading is ok, just make sure that the calculation producing the value you're setting is idempotent so that recompositions don't change the result.
One thing to avoid is, "backwards writes" - reading a snapshot state object in a different recompose scope that appears before the recompose scope it's written in.
If you were to write something like this:
Copy code
var counter = remember { mutableStateOf(0) }
Text("Count: $counter")
SomeComposable { // @Composable lambda parameter
  counter++ // backwards write!
  Text("New count: $counter")
}
then we're left with a host of bad options for interpreting what you meant in any sort of declarative way
Technically this code asks compose to perform an infinite loop
so don't do that 🙂
d

Dominaezzz

03/07/2021, 4:06 PM
Ahh makes sense. Thanks for explaining!
👍 1
Has this been considered or has a decision been made? Given stabilisation is happening soon.
a

Adam Powell

06/05/2021, 2:42 PM
Considered, yes, I'm still quite on the fence about it. I'm inclined to either leave it alone or mark
produceState
as
@Experimental
for 1.0
d

Dominaezzz

06/05/2021, 2:45 PM
@Experimental
would be ideal I think. At least it can be changed later if need be.
a

Adam Powell

06/07/2021, 9:23 PM
we came back to this earlier today and decided to leave it as-is and consider some companion APIs to go alongside it for related use cases
I'll update the bug from above with some of the thinking, happy to elaborate a bit here too
👍 1
d

Dominaezzz

06/08/2021, 7:38 AM
Thanks for looking into it! The explanation for leaving
produceState
as is makes sense to me. Thinking of it as a
switchMap
or
transformLatest
did the trick.
However, I now realise, based on the explanation, that I probably created a ticket for the wrong thing. What about
collectAsState
? It's continuity is handled by the upstream flow, at least for
StateFlow
. Plain
Flow
is somewhat debatable but I'm not fussed about it.
a

Adam Powell

06/08/2021, 1:37 PM
Treating collectAsState receiver changes like a switchMap is the same case
z

Zach Klippenstein (he/him) [MOD]

11/08/2021, 5:13 PM
Reviving this old thread since it came up again recently and I don’t see an obvious answer in that issue or this thread: Should the default behavior of
collectAsState
, or at least the overload with a
StateFlow
receiver, be to immediately return the new flow’s initial value when the flow changes? It is very counter-intuitive for it to do otherwise, so if not, why not?
Most of the discussion has been about
produceState
and it makes sense to leave that as-is.
d

Dominaezzz

11/08/2021, 5:24 PM
I still think collectAsState should be different for stateflow but I think that ship has sailed.
I've resorted to using state objects in my view model instead. It's somewhat awkward but whatever at this point.
5 Views