I am running into an issue with `routing-compose` ...
# compose-web
d
I am running into an issue with
routing-compose
where a flow that I am collecting as state does not trigger a recomposition if the page is loaded under a path that is being matched by the router (regardless of whether I'm using
HashRouter
or
BrowserRouter
This is the relevant line of code that is not triggering recomposition: https://github.com/dellisd/reroute/blob/master/web/src/jsMain/kotlin/io/github/dellisd/reroute/map/MapDemo.kt#L31 it is a child of a
HashRouter
https://github.com/dellisd/reroute/blob/master/web/src/jsMain/kotlin/io/github/dellisd/reroute/Main.kt#L22-L23 Here (https://dellisd.github.io/reroute/), the page is loaded and the data is loaded asynchronously before being emitted to a flow, collected as state, and then displayed on the map (as the set of red points) But here (https://dellisd.github.io/reroute/#/stops/7664) where a specific route is matched, the data is still loaded but now the flow does not trigger a recomposition in the
MapDemo
composable and the map is not updated to display the points Am I missing something, or is this potentially a bug somewhere? And if it is a bug, does anyone have any tips on how to narrow down where the bug is actually coming from?
s
I would log when composition happens vs when the value is emitted, probably you want to use
StateFlow
Most likely, the composable is recreated by router on navigation, so you essentially resubscribe after emission, only receiving empty value
d
I added an
onEach
to the flow in the composable to log when the flow emits, and it emits exactly twice (once with the default
null
value, and then once with an empty list) but never updates after the data is loaded asynchronously. Even if the composable is being recreated, I feel like it should be receiving at least one emission (the flow originates from SQLDelight) Changing it to a
StateFlow
did actually fix it, but only if I use a scope from
rememberCoroutineScope()
. Using any other scope doesn't work, and actually breaks other unrelated flows somehow, so now I'm passing a
CoroutineScope
into my viewmodel which doesn't feel right, but at least it works?
Copy code
fun stopData(scope: CoroutineScope) = flow {
    emitAll(dataSource.getStops().map { list ->
        // ...
    }.stateIn(scope, SharingStarted.Eagerly, null)
h
Hey, yes. Routing-compose triggers a recomposition when a (new) route matches the path.
Thanks, I will take a look and will add a test.
I think, you just need to add
remember
to get the same (cold) flow:
val data by remember { viewModel.stopData }.collectAsState(null)
d
Unfortunately adding the
remember
didn't change anything
Woops, actually it looks like that did the trick! The page just wasn't on the latest build yet
Actually, it looks like it only works if I add
.onEach
to the flow , like
remember { viewModel.stopData }.onEach { }.collectAsState(null)
I added one to log the emitted items to the console and that's when it started working
Kind of odd, but at least it works
h
that's strange 😄
s
Atm you create a flow each time the function is called, I would just have
StateFlow
as a field inside view model and just expose getter, that way it is only created once on view model init and not each time you call the method
h
But when will Compose recalculate the remembered value (flow), when do you use it without a key? Should you really use
StateFlow
, if you only consume the
StateFlow
exactly in one Composable function via
remember { }.collectAsState()
? Edit: found it:
Copy code
Note: remember stores objects in the Composition, and forgets the object when the composable that called remember is removed from the Composition.
https://developer.android.com/jetpack/compose/state#state-in-composables So, using a state flow is way more efficient when using the flow in multiple views, especially with IO operations (sqldelight), to run the sql query only once at the start.
s
I just see the state flow as a way to keep the value + updates, the same as behavior subject (Rx) or LiveData(Android). If the injected model is always the same and the state flow is just a field there, you are guaranteed to get the latest values without compose magic of remembering and triggering the extra downloads, as long as view model is alive. This just assumes that the view model is already "remembered" somewhere, either through compose or di