Thread
#compose
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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
    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
    Arkadii Ivanov

    Arkadii Ivanov

    1 year ago
    How the state is represented in your external model?
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    It is regular data class like
    data class AppState(
        val screens: List<Screen>,
        etc.
    )
    Arkadii Ivanov

    Arkadii Ivanov

    1 year ago
    Could you show how is it stored?
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    what’s
    setContent
    in your case? can you post the actual code by any chance? the devil’s usually in the details
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    Here it is
    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
            )
        }
    }
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    what’s frontScreen’s type?
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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)
    Arkadii Ivanov

    Arkadii Ivanov

    1 year ago
    Do you use Compose's
    State
    ,
    Flow.collectAsState
    or similar things?
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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 SideEffects and use
    mutableStateOf
    but still the bug reproduces
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    i thought i recognized some old crossfade logic in there 😂
    can you share
    ComposableSwitcherState
    ?
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    It’s there, at the bottom 🙂
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    Agreed. I need to try rewrite this with proper snapshot-backed state again
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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()
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    Ah, so you are saying that scope’s
    invalidate
    invalidates only this particular node, not the complete subtree of that node?
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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 😄
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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
    i would also look at the Jetpack Navigation compose integration as an exemplar – same sort of idea, but straight from the horse’s mouth
    Vsevolod Ganin

    Vsevolod Ganin

    1 year ago
    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
    ).