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

harry.singh

03/19/2022, 10:23 PM
Hey all! I'm collecting a
Channel(Channel.BUFFERED)
as a flow in a Composable function but I'm only ever seeing the latest element and missing all of the elements before the latest one. I've added all of the code here. I debugged
collectAsState
and I'm seeing all of the elements in the
produceState
composable function but not in my composable function. Any ideas why this might be happening?
a

Arkadii Ivanov

03/19/2022, 11:02 PM
I believe this is by design.
collectAsState
means the value represents a state, not an event. So there is no need to render intermediate states, only the latest one.
☝🏻 1
l

lesincs

03/20/2022, 5:21 AM
yeah, I agree with Arkadii, that’s how state management works, it only cares the latest value. And in your case, I think you can just use
LaunchedEffect
, and then collect your flow directly.
2
h

harry.singh

03/20/2022, 2:08 PM
Makes sense! But what really happens with the intermediate states? Seems like they are just being ignored but how does compose identify a value as final state? Is there a timer in there somewhere?
z

Zach Klippenstein (he/him) [MOD]

03/20/2022, 4:48 PM
Kind of. The “timer” is the choreographer, ie the thing scheduling frames. When a snapshot state value is changed, compose goes “ok you changed Foo, and Foo was read by composables A, B, and C, so I'm going to make a note to recompose those whenever the next frame happens.” Compose doesn't do anything immediately when you change a state value, just invalidates whatever read it. Then when the next frame is being prepared, any composables that read state values that changed since the last composition are recomposed.
h

harry.singh

03/20/2022, 7:53 PM
In my case, I'm never using collected state in a compose/UI code, so is there anything to invalidate here? My objective is to handle events independent of the UI but in a composable function. When the channel collection code see a new value from the channel, does Compose invalidate the code that handles those events?
a

Arkadii Ivanov

03/20/2022, 8:05 PM
If you don't need to update the UI, then just collect the Flow inside LaunchedEffect. But if you need to update the UI, then in addition to the collection you also need to change the state. You can define
val state = remember { mutableStateOf(...) }
and update it when events are received. The UI will recompose automatically.
h

harry.singh

03/21/2022, 1:03 PM
I tried collecting flow with
LaunchedEffect
but I'm still seeing only the latest event in that collection as well. Here's my code to collect that flow:
Copy code
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(key1 = true) {
    coroutineScope.launch {
        viewModel.eventsFlow.collect {
            when (it) {
                // Not seeing Event1
                is Event1 ->
                is Event2 ->
            }
        }
    }
}
The event generation flow is unchanged and Event1 is emitted before Event2
a

Arkadii Ivanov

03/21/2022, 1:28 PM
The event might be produced before you actually start collecting, I would check this first. Also, perhaps you don't need the scope, because it is already managed by LaunchedEffect
h

harry.singh

03/21/2022, 1:48 PM
The event might be produced before you actually start collecting, I would check this first.
I checked but those events are fired in response to a network response and by the time we receive network response, the composable is already shown on the screen in a fragment, meaning we start collecting it immediately
a

Arkadii Ivanov

03/21/2022, 1:52 PM
What is type of the Flow? If it is a StateFlow then it will drop all emissions except the latest one. StateFlow is conflated.
p

Paul Woitaschek

03/21/2022, 1:53 PM
I would not do any of this in a composable but rather in some form of a view model and manipulate the state from there
2
h

harry.singh

03/21/2022, 2:03 PM
@Arkadii Ivanov It is a flow backed by a channel
Copy code
class MyViewModel: ViewModel() {
    
    sealed class MyEvent {
        object Event1: MyEvent()
        object Event2: MyEvent()
    }
    
    private val eventsChannel = Channel<MyEvent>(Channel.BUFFERED)
    val eventsFlow = eventsChannel.receiveAsFlow()
    
    val state = fetchSomData()
    	.onEach {
            eventsChannel.trySend(Event1)
            eventsChannel.trySend(Event2)
        }.stateIn(
            viewModelScope,
            SharingStarted.Lazily,
            someInitialValue
        )
}
z

Zach Klippenstein (he/him) [MOD]

03/21/2022, 2:28 PM
Do you ever have a case where there are multiple
eventsFlow
collectors at once?
Also, your launched effect key is wrong, it should be
viewModel
.
a

Arkadii Ivanov

03/21/2022, 2:35 PM
Yeah,
receiveAsFlow
splits events between multiple subscribers, as per the documentation. You may want to use
SharedFlow
.
🙏 1
h

harry.singh

03/21/2022, 2:42 PM
Do you ever have a case where there are multiple
eventsFlow
collectors at once? (edited)
Yep, there was another collector which I thought I had removed. It is working as expected now 🙂
Also, your launched effect key is wrong, it should be
viewModel
.
Why is
viewModel
the correct key in this case and
true
an incorrect one?
z

Zach Klippenstein (he/him) [MOD]

03/22/2022, 3:43 PM
The high-level docs cover this. The launched effect will capture a reference to all the values you reference inside of its lambda. If those values change between compositions, without a key, the effect will continue running with stale values. You should always pass any dependencies as keys to your effects that come from composition, and are not stored in state holders like
MutableState
to ensure that the effect will be restarted if the dependencies change. In this case, if the thing giving you a ViewModel changes, your effect should restart to re-capture and restart using the new view model.
I had a twitter thread about this recently as well: https://twitter.com/zachklipp/status/1504110707419148291
p

Paul Woitaschek

03/22/2022, 3:58 PM
@Guilherme Almeida That tweet from Zach very much looks like what we discussed earlier ☝️
g

Guilherme Almeida

03/22/2022, 4:05 PM
Regarding the textfield focus leading to recomposition of other fields?
p

Paul Woitaschek

03/22/2022, 4:06 PM
The recomposition one
g

Guilherme Almeida

03/22/2022, 4:09 PM
I’ll investigate it 🔍
5 Views