Please advise how can I debug situations when comp...
# compose
v
Please advise how can I debug situations when composable doesn’t get recomposed when it should (it seems)? For all I know, I click something inside UI, the callback updates external model (completely outside Compose), then reactive forces get to call
setContent
on Activity and then just nothing - nothing updates on screen, nothing bumps into my breakpoints except top-level composable. Don’t know where to look honestly
1
More dreadful is that it is not stable - sometimes it works, sometimes it gets completely stuck for the rest of view’s life
Side note: when I attach Layout Inspector, it finally updates after being stuck
a
How the state is represented in your external model?
v
It is regular data class like
Copy code
data class AppState(
    val screens: List<Screen>,
    etc.
)
a
Could you show how is it stored?
v
It’s basically a
StateFlow<AppState>
in val in activity which I save and restore additionally. And then I have my update in
MainScope.launch { stateFlow.collect(::setContent) }
setContent
gets called every click/update, that’s for sure
And the state really gets updated and passed to that
setContent
call
z
what’s
setContent
in your case? can you post the actual code by any chance? the devil’s usually in the details
v
Here it is
Copy code
private fun setContent(appState: AppState) {
    val frontScreen = appState.backstack.screens.frontScreen() ?: return
    val frontScreenPosition = appState.backstack.screens.frontScreenPosition()
    val drawerState = appState.backstack.drawerState

    setContent {
        ContentView(
            screen = frontScreen,
            positionInBackstack = frontScreenPosition,
            drawerScreenState = drawerState,
            dispatch = ::dispatch
        )
    }
}
z
what’s `frontScreen`’s type?
v
Copy code
sealed class Screen : Parcelable {

    data class ... : Screen

    data class ... : Screen
}
All of them are
data class
’es and it breaks exactly when another screen should pop up. For screen transition I use my custom thingy (sorry, not trying to advertise it). Additionally it is wrapped with
Scaffold
. I strongly suspect that it may be a bug in my screen transition code but no luck fixing it so far
I tried Compose Backstack by the way, it’s great, but it doesn’t suit me fully since I separate keys and values. And I want to store values additionally when transition is in motion
z
ah, that stuff can be hard to get right – i’d be suspicious of it too (i’ve written backstacks a few times now and it took a while to get it right lol)
👍 1
a
Do you use Compose's
State
,
Flow.collectAsState
or similar things?
v
I use
mutableStateOf
for internal composables states, yes. I’m not using
Flow.collectAsState
, there is no single composable that has
Flow
as input in my code
My transition code obviously lacking some proper handling of local state mutation (which is not even a
State
!) but that’s just because it’s based on old
Crossfade
implementation. I tried to add proper `SideEffect`s and use
mutableStateOf
but still the bug reproduces
z
i thought i recognized some old crossfade logic in there 😂
can you share
ComposableSwitcherState
?
v
It’s there, at the bottom 🙂
z
that manual invalidation in a
SideEffect
seems a bit smelly
derp
ok, so there’s a lot going on here, and i’d probably need to read through this about 10 times to be confident i understand all the details, but first thing i noticed is line 49 you’re mapping into a non-snapshot
MutableList
in your state object. That list is iterated over in the
Box
, but because it’s not a snapshot list, that
mapTo
probably isn’t going to trigger a recomposition in at least some cases
i bet factoring most of this code into your state object would make it a bit easier to reason about since you don’t have to keep thinking in composable mode
v
Agreed. I need to try rewrite this with proper snapshot-backed state again
z
I think you’ve tried to solve that problem by manually invalidating, but the recompose scope you’re grabbing isn’t the same one inside that
Box
lambda, so i think it’s not doing what it’s intended to (i.e. recompose the list)
i’m not sure it will fix it, but first thing i’d try is changing line 91 to be a
mutableStateListOf()
v
Ah, so you are saying that scope’s
invalidate
invalidates only this particular node, not the complete subtree of that node?
z
i believe that’s how it works, but i could be wrong. But if that is how it works, it might explain your bug
the whole idea of recompose scopes is that they’re independent – that’s what allows them to be composed in parallel on multiple threads
v
Ok, I see, that makes a lot of sense. I try to rewrite this with snapshots and see if it helps. Thanks!
By the way,
Crossfade
is now a bit different from that time, maybe I will learn a trick or two from its code again 😄
z
i also very recently (last weekend) rewrote most of the compose-backstack logic, and fixed some more of my own bugs. Not sure if it will help or hinder as an example but if you’re interested take a look at TransitionController
👍🏻 1
i would also look at the Jetpack Navigation compose integration as an exemplar – same sort of idea, but straight from the horse’s mouth
v
So I fixed it, hopefully. Regarding the initial question of how to debug when composable isn’t getting called when it seems that it should - no other method so far except doing some detective work and be aware of how recomposition works in general and making assumptions of how your code works with it. The assumptions may be wrong, so you should check that fairly often too (by re-reading docs or posting a question in this slack maybe 🙂). In my case I had to be more aware of mutating and using local properties that are not backed by snapshot (i.e.
mutablesStateOf
,
mutablesStateListOf
).