Hey, I’m trying to use `navigation-compose` with v...
# compose
d
Hey, I’m trying to use
navigation-compose
with viewmodels scoped to
NavBackStackEntry
to pass complex parcelable arguments since it’s not possible natively from list to detail screen. It works fine but when I press button it looks like the detail screen is “requested” again in
composable
call but the previous back stack entry is null. Details in 🧵
👀 1
c
@david.bilik can you edit your post to put the code in this thread please? https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616265877303000
2
d
When I’m navigating from the list to detail I have this code
Copy code
val navArgVM = viewModel<StreamDetailNavViewModel>rootNavController.currentBackStackEntry!!)
Card(
        modifier = Modifier
            .clickable {
                navArgVM.setStreamUri(uiStream.stream.url.toString())
                rootNavController.navigate(
                    Destination.StreamDetail.route
                )
            },
    )
and when I’m reading it when navigating I have something like this
Copy code
NavHost(rootNavController, startDestination = Destination.Main.route) {
  composable(Destination.StreamDetail) {
     rootNavController.previousBackStackEntry?.let { prevBackstackEntry ->
        val navArgVM = viewModel<StreamDetailNavViewModel>(prevBackstackEntry)
        StreamDetailScreen(navArgVM.getStreamUri())
     }
  }
  ...
my problem is that this
composable
is called on back press click and in that situation
previousBackStackEntry
is null which will for a brief momenty display my
windowBackground
before the list is displayed again (hence I don’t emit any Composable in that case). Is this even the correct way how to tie viewmodel to the backstack entry?
🙏 1
i
Why are you doing this at all vs just passing the stream URI as a regular argument in the route?
Copy code
rootNavController.navigate(
  Destination.StreamDetail.route + "/" +
  Uri.encode(uiStream.stream.url.toString())
)
d
in the example it’s just string since it’s my first screen i’m refactoring but i have other screens that have complex arguments and I want to have it consistent
i
The consistent and correct way to pass arguments is to pass them via your route. There's nothing consistent or recommended with this attempt
Maybe you should explain what cases you have that can't easily be solved via arguments first, rather than start with a case that works with arguments just fine. That might help us understand and suggest what you actually should be doing
Because it sure isn't this
d
I wrote the app with compose being in beta and previously I’ve passed arguments through arguments (
Bundle
) of nav stack entries. My app is small, apx. 5 screens, but I’ve passed around
NavArgs
classes which were parcelables. Now I’m migrating this app to compose
1.0.1
and my previous approach is not working so I’m trying to find a way how not to rewrite all of the arguments to query strings because it would require more work than this approach which I’ve found in this discussion
c
Not being too helpful here except saying that complex args are actively discouraged in the docs. I would just rewrite it as it seems safer, supported, and you know it'll work.
d
yea I know, I’ve read it and while disagreeing with that decision it looks like I have no other choice. Thanks to both of you anyway
s
I wish, Docs also state how to handle complex Object keys. Which involve Repository layer.
👍 1
c
Just send an id and retrieve your actual complex object from it's source of truth vs sending a snapshot of this complex object at a specific point of time.
It gets easier if you just think of it as a web url. You don't see full on json objects in a url. You see an id, and then the page retrieves based on that id.
Sending a complex object leaves you prone to stale data and hitting the binder transaction limit (which keeps shrinking in every version of android).
d
i understand the reasoning and i also know how to achieve that, i just dont think it’s always the best way and it requires a lot of boilerplate
1
s
@Colton Idle Yeah, i agree, also don't like to pass Parcable as well, but just having a Advance example will be better, maybe in Compose samples.
c
I would say that it is the best way, because it doesn't get you into a state where you're showing stale data after process death. "boilerplate" is something I can more agree with, but if you want, just save your data in a larger scope that can be accessed by both screens. If you use nav-compose then you can use nav scoped view models for this which is really handy. Or just on the activity level or even app level.
d
nav scoped view models
is exactly what I was trying to achieve but probably in a wrong way
💯 1
i
No, you're trying to reach into some other screen's ViewModel (that's not the right approach and not the kind of coupling you should ever do). That's very different from what Colton was talking about about having a ViewModel scoped to a navigation graph accessible by all destinations within that graph. And that still isn't what you should be doing to pass arguments between screens
It would be really helpful if you had a concrete example that we could brainstorm over how exactly you should approach the problem, focusing on your source of truth for the data (which is absolutely critical for reactive UI toolkits like Compose)
👍 1
d
🆗 I have a bad API i definitely can’t change that returns me a list of objects (in this case it’s a description of some videoarchive item). It does not have an endpoint to retrieve detail, it has just this API for list and it’s paged. So what I did is that I’ve passed this object (parcelable) of list item to the detail screen where i displayed name, date and some other metadata and I’ve called another endpoint to retrieve some sub entities. The app is online only without any caching in place. So if I would need to implement some sort of caching I would probably need to either implement database, data store, shared prefs or file caching just for passing this because even though API is the source of truth, I don’t have a way how to retrieve that specific item from the API based only on ID. What I could do would be to unmarshall the object to it’s primitve types, passed in the route (query string) and then put it back, but it’s also not an elegant solution
i
Great, so what component in your app calls that 'bad API'? Usually you try to have some layer between your UI and direct network access, right?
1
d
Yep, repository calls a data source, list access this repository through viewmodel. Detail viewmodel asks another repository for data tied to that screen. List item is passed through parcelable argument as stated above.