Hi, what advantages would I have when I collect a ...
# compose
l
Hi, what advantages would I have when I collect a flow directly in composable function instead of collecting it in view model? And when would I prefer one over the other?
a
by, "in view model" you mean using
myViewModel.viewModelScope
? Collecting a flow in a composable function will have a much narrower scope, it will start collecting when that composable enters the composition and stop collecting when it leaves. `viewModelScope`s live much longer and will leave the flow collecting and potentially performing work when nothing needs it.
If you don't want to start and stop collecting rapidly if different composables that use the same flow enter and leave frequently, the
.shareIn
flow operator lets you specify a timeout; you can
shareIn
your
viewModelScope
and use such a timeout to keep unnecessary work to a minimum: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-sharing-started/-while-subscribed.html
👍 1
whether
shareIn
or
stateIn
is more appropriate depends on whether the flow carries events or state
if you use
shareIn
or
stateIn
, then you can collect the resulting shared/stateflow in your composable but there will be only one underlying collect on the upstream flow while one or more subscribers (i.e. composables listening to the flow) are active
something like location updates via a
channelFlow
is a good example of when this is useful
r
@Adam Powell if we implement any device connection (via USB, Bluetooth, Ethernet) is it a good decision to create connect function returning flow of connection state (Connecting, Connected, Disconnected)?
And also if we want to read incoming stream create functio startReading returning shared flow of bytearaay so if there is any consumer it will continue reading input stream and when all consumers stop flow also stops reading stream
a
this is getting a bit more into general flow design 🙂
l
Thanks for your answer.
Copy code
by, "in view model" you mean using myViewModel.viewModelScope ?
yes, to be more concrete I mean doing sth. like this:
Copy code
private val _state: MutableStateFlow<ConnectBluetoothDeviceUseCase.BluetoothConnectState> =
        MutableStateFlow(
            ConnectBluetoothDeviceUseCase.BluetoothConnectState.Loading
        )

    fun connect(device: BluetoothDevice) {
        viewModelScope.launch {
            connectBluetoothDevicesUseCase.connect(device).collect { connectState ->
                _state.value = connectState
            }
        }
    }
@rsktash We have the same intention. @Adam Powell I was wondering if I should avoid collecting the state that comes from my
ConnectionUseCase
then updating the
StateFlow
in favor of collecting it directly in composable. Would it be a better solution?
a
lots of hot/cold, factory vs. single connection state management questions in here
start simple and only add complexity when you need it; when you have a flow, you have a factory for subscriptions to a cold data source. Sometimes that data source is shared or otherwise hot, but downstream collectors are able to ignore the difference.
Assume that each
collect
to something like a remote device will establish its own new connection to it, and layer any sort of sharing on top.
In the snippet above, you're rolling your own sharing. This is going to carry some other complexity; for example, what happens when you call
connect
multiple times?
Can you connect to more than one device at a time? Should it maintain only one connection and close the old one when you switch devices?
l
There will be only one connection at time in my case. Before calling the connect function I naviagte to a new screen so I don't have to worry about multiple connections and device switching. When I leave the new screen, the connection is closed
r
@Lilly there you are passing device which means one state for many device connection. I think it will be right decision to return state flow via connect function
a
If more than one thing wants to know the connection state of the same connection, how do those other collectors gain access to it?
l
@Adam Powell I will have only one subscriber. It's jsut the question where to collect the flow. The possible states would be Loading, Success or Failure,
a
if it's a hot flow updated as a side effect of the connection itself, and the connection is managed elsewhere anyway, it doesn't particularly matter where you collect a flow of just the connected/disconnected state. I might even skip pushing that into a StateFlow entirely and just change a
mutableStateOf
instead, if the primary consumer of that information is your compose ui
r
There is also a shared flow where you can decide to emit or not, connect or disconnect via collecting subscriptionCount
l
Good point. What is the difference between between
mutableStateOf
and
StateFlow
. They both have the same behavior not? -> to notify ui on changes
a
if you're doing the
Copy code
private val _foo = MutableStateFlow<Type>(...)
val foo: StateFlow<Type> get() = _foo
thing and you're just setting
_foo.value = newValue
as things happen, that translates pretty directly to
Copy code
var foo by mutableStateOf<Type>(...)
  private set
and is generally nicer to work with. And if some other code outside of a composable function wants to observe changes to it, it can use
snapshotFlow { myObject.foo }
and collect that. Composable functions can just refer to
foo
directly and the change observation happens automatically.
l
Alright, thank you very much
👍 1