Hi people! I was thinking let’s rehash the compos...
# compose
l
Hi people! I was thinking let’s rehash the compose navigation + parcelable discussions. What does the future look like? Will the be a, decent, official solution for this? I get that someone is advocating single source of truth etc, but IMO there are mitigating circumstances when migrating code and also when doing larger reusable components. Gradle modules also cause friction with the single-source-of-truth idea. I just don’t get why compose needs to be overly opinionated about routes and NOT parcelables? (Excuse me if this has been recently discussed; I found two threads, 1 from august last year and 1 from october 2020) To put it plainly, I want to just serialise my parcelables, base64 encode them, and pass them through. At least until a viable official alternative is in place.
i
To put it plainly, I want to just serialise my parcelables, base64 encode them, and pass them through. At least until a viable official alternative is in place.
That's already officially supported, with whatever encoding you want: https://developer.android.com/guide/navigation/navigation-kotlin-dsl#custom-types
That doesn't make it a good idea though, that part is as true as ever 🙂
l
I get that, but will there be any other alternatives?
i
To do what exactly?
l
Do navigation more like before with parcelable 😋
i
Passing parcelables between screens is just as much of an anti-pattern in the 'before' world as it is in Navigation Compose
Navigation Compose navigates via routes. Routes include all of the arguments you want to pass to a destination. That's not going to change
If you have a specific case in your app where you aren't sure how to approach it, it would be helpful if you were very specific about your architecture setup and what you are trying to do
l
Hi! I’m back again from a family trip to the UK (London). Anywho. Our architecture is nothing special it’s quite up to par with MAD. Due to legacy we still have tons of views, quite a lot of activities and still some fragments. We cache things locally in room, but all the underlying db’s are pr. feature. Converting to Compose has up until recently gone pretty smoothly, sure there has been hickups, but this navigation hurdle has been the biggest hurdle for us thus far. We’re a payment/banking app and in this particular hurdle we’re about to settle a shared expense between a number of people. We’re probably in screen 3 out of 5 and our current architecture require us to pass quite a lot of data to the next step, this data has been extracted from various places and assembled in the VM for this step. Data includes payment-related details, group/recipient-details, event-data-origin-stuff and probably more. In the view-world this would have been passed as a parcelable in the fragment arguments bundle.
i
Sounds like exactly the use case for a shared ViewModel scoped to a navigation graph that encapsulates that entire user flow that all screens use as the source of truth and something that should have never been fragment arguments
l
I think I’ve been confused with some of the other proposed solutions I’ve seen to this problem. A self-contained
Navigator
which is a
ViewModel
which just caches these arguments, and also has access to a
NavController
— really rubbed me the wrong way. I can get on board with ViewModel scoped to a sensible place in the navigation graph. Just so we’re on the same page, this is now the recommended way to access the back stack entry?
Copy code
composable("A") {
    ScreenA(hiltViewModel(it))
}

composable("B") {
    val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
    ScreenB(hiltViewModel(parentNavBackStackEntry))
}

composable("C") {
    val previousBackStackEntry = remember(it) { navController.previousBackStackEntry }
    ScreenC(hiltViewModel(previousBackStackEntry))
}

// Or nested navigation where we say "A" is the owner:
navigation(startDestination = "A", route = "HOME") {
    composable("A") {
        ScreenA(hiltViewModel(it))
    }

    composable("B") {
        val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
        ScreenB(hiltViewModel(parentBackStackEntry))
    }
}
i
None of those examples are using a shared ViewModel scoped to a navigation graph (you're just directly accessing some other destination's ViewModel..): in your example, scoping to a navigation graph would be scoping something to
"HOME"
via
getBackStackEntry("HOME")
l
that is what “B” is doing
Copy code
composable("A") {
    ScreenA(hiltViewModel(it))
}

composable("B") {
    val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
    ScreenB(hiltViewModel(parentBackStackEntry))
}

composable("C") {
    val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
    ScreenC(hiltViewModel(parentBackStackEntry))
}

// Or nested navigation where we say "A" is the owner:
navigation(startDestination = "A", route = "HOME") {
    composable("A") {
        ScreenA(hiltViewModel(it))
    }

    composable("B") {
        val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
        ScreenB(hiltViewModel(parentBackStackEntry))
    }

    composable("C") {
        val parentBackStackEntry = remember(it) { navController.getBackStackEntry("A") }
        ScreenC(hiltViewModel(parentBackStackEntry))
    }
}
i
Nope, that's just reaching into destination A
l
ok…
i
Those two screens are siblings, not a parent/child - otherwise you'd see a
navigation
element as one of them
l
Contrary to the impression I seem to give off, I have actually read that.
i
And if you look at the hilt docs, you'll note that it is using the route of the navigation graph, not one of an individual destination: https://developer.android.com/jetpack/compose/libraries#hilt-navigation
That's the bit that the code snippet you've included isn't doing
l
Copy code
navigation(startDestination = "A", route = "HOME") {
    composable("A") {
        val backStackEntry = remember(it) { navController.getBackStackEntry("HOME") }
        ScreenA(hiltViewModel(backStackEntry))
    }

    composable("B") {
        val backStackEntry = remember(it) { navController.getBackStackEntry("HOME") }
        ScreenB(hiltViewModel(backStackEntry))
    }

    composable("C") {
        val backStackEntry = remember(it) { navController.getBackStackEntry("HOME") }
        ScreenC(hiltViewModel(backStackEntry))
    }
}
like so then
I think we dropped the ball on that small tidbit. It’s a useful feature we somehow missed.
Ie. the difference between the
navigation
builder and children vs siblings vs hiltViewModel
i
Yes, that would be having all screen access the same shared ViewModel scoped to the navigation graph they are a part of
l
Thanks 👍 🤗