Derek Ellis
03/18/2022, 5:22 PMrouting-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?shikasd
03/19/2022, 1:54 PMStateFlow
Most likely, the composable is recreated by router on navigation, so you essentially resubscribe after emission, only receiving empty valueDerek Ellis
03/19/2022, 6:49 PMonEach
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?
fun stopData(scope: CoroutineScope) = flow {
emitAll(dataSource.getStops().map { list ->
// ...
}.stateIn(scope, SharingStarted.Eagerly, null)
hfhbd
03/20/2022, 1:53 PMhfhbd
03/20/2022, 1:53 PMhfhbd
03/20/2022, 6:47 PMremember
to get the same (cold) flow:
val data by remember { viewModel.stopData }.collectAsState(null)
Derek Ellis
03/20/2022, 10:56 PMremember
didn't change anythingDerek Ellis
03/20/2022, 11:01 PM.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 workingDerek Ellis
03/20/2022, 11:01 PMhfhbd
03/21/2022, 7:59 AMshikasd
03/21/2022, 5:31 PMStateFlow
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 methodhfhbd
03/21/2022, 5:38 PMStateFlow
, if you only consume the StateFlow
exactly in one Composable function via remember { }.collectAsState()
?
Edit: found it:
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.shikasd
03/22/2022, 2:33 AM