How to correctly pass parameters with Compose navi...
# compose
p
How to correctly pass parameters with Compose navigation if you must use enums for the routes? In the docu they show you this example for passing parameters:
Copy code
NavHost(startDestination = "profile/{userId}") {
    composable("profile/{userId}")
}
But how to work with enums for the routers? in previous codelabs and docu they told you that you must use enums for the routes:
Copy code
NavHost(startDestination = Screen.HomeScreen.name) {
    composable(Screen.HomeScreen.name)
}
How to pass parameters correctly using enums for the routes? How to add that userId parameter if you use an enum for the route?
s
2 options: • You have to manually wire up the code necessary to have the parameters both read from the route, and when navigating to that route. See an example inside NowInAndroid. • Use navigation-compose-typed which does this work for you internally by using kotlinx.serialization (1 future option) • Wait for this https://issuetracker.google.com/issues/188693139 to be merged and you’ll get the behavior of navigation-compose-typed (more or less) natively shipped by the androidx.navigation library
😮 1
🌟 1
p
😕
too much complex I think
I'll wait and for now I'll concatenate it as a string to the enum route
something like this
Copy code
composable(route = Screen.PlacesScreen.name+"/{selectedCategory}")
I don't like any of these three options, they add too much complexity for simply passing a parameter
s
It’s up to you. This approach that you suggest relies on your remembering to do this each time on each call-site and does not take care of what happens if the parameter that you pass in is not properly URL encoded and so on. Which is something that you need to handle if you want to be 100% correct here. Also your
composable(route = Screen.PlacesScreen.name+"/{selectedCategory}")
suggestion does not add the right arguments to the route as you you can see in the first link
Copy code
arguments = listOf(navArgument(TOPIC_ID_ARG) { type = NavType.StringType }),
Also something that you will need to do anyway, you can’t go around that. Both option 1 and 2 have all those problems covered. So it’s up to you to decide what you want to do now that you know what you need to think about 😊
p
I understand you, but I'm trying to add the lowest complexity possible because compose is still hard to understand for me, coming from sequential programming Its very hard for me to change to declarative programming
for example, now I'm stuck again, because using navigation in compose, hosted in the main app composable, I have two screens, screen A allows user to select a category, and that category must be passed to screen B as a parameter. The problem I found is that when you select the category in screen A, you have it on the state ui inside the viewmodel of that screen A. I know that I must pass that variable to the route of screen B using navigation arguments, but I don't know how to access that variable in the main app composable that hosts the navhost. I mean... the navhost doesn't have access to viewmodel of screen A, so, how can it knows that parameter that needs to pass to screen B in the navigation argument?
s
Realistically, adding
navigation-compose-typed
is by far the simplest think you can do for this. All you’ll need to do is change the imports to use the right function from inside that library, add the serialization plugin, and mark your destinations are serializable. That’s it.
Your composables should just call a lambda where they just pass up the parameter that they want to use while navigating, and you can hook that up to call the right navigation call at the place where you are creating your nav graph. This thread https://kotlinlang.slack.com/archives/CJLTWPH7S/p1645595702068129?thread_ts=1645552485.247699&cid=CJLTWPH7S has more info.
p
Thank you, now I have the parameter, but how can I pass the parameter to the viewmodel of the screen B?
Copy code
class UserViewModel(
    savedStateHandle: SavedStateHandle,
    private val userInfoRepository: UserInfoRepository
) : ViewModel() {
    private val userId: String = checkNotNull(savedStateHandle["userId"])
    private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(userId)
}
p
but... they don't say how to set the value to the viewmodel
only says how to recover it
s
You "set" it by navigating to the route with the right parameter. If you go to
link/blah/3
for route
link/blah/{someId}
then it's
3
p
😮
ok, works like magic then, I'll try to memorize it
s
That's the whole point of routes with parameters. Wouldn't call it magic, unless you consider websites also magic when you go to a url which also has parameters 🤷‍♂️
p
why in that sample they are passing a second parameter on the viewmodel? how and where is set that parameter and why is not a livedata, remember etc... parameter?
private val userInfoRepository: UserInfoRepository
well in this case seems very different from a website for me, because it's being received by the viewmodel, not by the composable, and I'm not setting it on the viewmodel at any point
s
You can absolutely get it in the composable too. Inside the composable lambda you have a NavBackStackEntryb which contains the route, the parameters passed into it etc. The fact that the ViewModel even gets those values is just the default behavior of the
viewModels
function. Again, not magic.
why in that sample they are passing a second parameter on the viewmodel? how and where is set that parameter and why is not a livedata, remember etc... parameter?private val userInfoRepository: UserInfoRepository
You will probably benefit from reading the docs a bit https://developer.android.com/topic/architecture
p
thank you, and what about that second parameter on the sample?
s
^^ my message above
p
well that's a generalistic article about app architecture which I already have been readed
can't figure out how can that help me understand why that viewmodel is receiving that second parameter
as I can remember, for setting parameters on viewmodels you are forzed to use factory patern
and without by remember, or mutablestate etc... the parameter will not be remembered in recompositions
so I can't understand why they are using that parameter there
s
How you construct it has nothing to do with the fact that in that use case, it needs to use the repository to get access to... well stuff that the repository contains. Not sure how any of the rest of what you're saying here is relevant. They're giving an example of a VM having some other parameter as well.
And they're showing how to be able to call that
getUserInfo
function from the repo using the parameter which has come from the route parameters. That's it.
p
so you mean they are using a factory pattern to make viewmodel accepting that second parameter but they are not showing it on the sample? or you mean that in compose you can use viewmodels with parameters without needing to use factory pattern to add parameters to viewmodel?
s
How it's constructed is irrelevant to the sample given here. You may be doing it yourself with a factory, you may be using the built-in functions by hilt, or even by koin. It doesn't matter in this context.
p
in that sample, they are not using hilt or koin, so you mean that they must be using
Copy code
ViewModelProvider.Factory
but they are not showing it
am I correct?
I'm sorry to ask you all those questions, this is my only way to understand things that reading documentation or programming samples doesn't allow me to understand
s
What I'm thinking to help you understand is that it's a sample, so they only show the part relevant to the sample. It doesn't matter what one may use on the other side. In your app, you use what you need to use to make it work. If you're struggling with that part, there must be some other part of the documentation which explains how to actually get the ViewModel instance from inside your composable, with hilt, with whatever. I don't have an answer to your question because this is not a real app, it's a sample so I can't know what they're using because there's nothing on the other side, it's just a sample. If you're having trouble getting this to work on your app I suggest looking for a different part of the docs once you've understood this part.
p
thank you very much
🙌 1
I understand you
it is working now with your explanations and help
s
Glad to help! 😊
p
🙂
324 Views