I am trying to pass, args with savedStateHandle wi...
# compose
n
I am trying to pass, args with savedStateHandle with navigation compose, but when I scope the hiltViewModel to my navgraph, those args are not passed with savedStateHandle, I saw AssistedInject, but it doesn't survive process death,unlike savedStateHandle. Is there a way to share those viewmodel, while able to pass args through savedStateHandle?
s
How are you grabbing your VM in your composable? And what is the route you're scoping it to looking like? I think it's ssh should just be populated properly depending on which route you're scoping it to.
n
Copy code
composable<ProblemSelectionScreen> {
    val graphEntry = navController.getBackStackEntry(CreateJobCardGraph)
    val viewModel: ProblemViewModel = hiltViewModel(graphEntry)
    val customerDataViewModel: CustomerDataViewModel = hiltViewModel(graphEntry)

    ...
}
I am sharing VM this way, where the
CreateJobCard
is the graph, in which all the screens where I share the VMs are present
s
Right, and you say that the SSH does not contain the arguments from the
CreateJobCardGraph
route then? Or that the two viewmodels do not receive the values inside their respective SSH passed into them?
n
Yep the two VMs doesn't receive the args passed to their screens, when I debug, I am getting their elvis operator values, means I am getting null
@Serializable
data class ProblemSelectionScreen(val customerId: Int = -1, val regNo: String = "")
a
The composable function itself provides the backstack entry as the lambda first argument ..why would you need a different back stack entry?
s
If the VM is scoped to the
CreateJobCardGraph
how would it receive the arguments from the specific screen? It's not scoped to that at all, I do not think it even knows about that more specific screen in that case.
why would you need a different back stack entry
Looks to me that they're scoping them to the graph in order to have the VM survive for as long as the graph is in the backstack and not the specific screens. Also to share the VMs across those destinations.
👍 1
n
Yes, so I can't use the args passed to the individual screen, if my screens are scoped to my graph right? what can I use as alternative ?
s
Afaiu, hiltViewModel just delegates to
viewModel
lambda, which eventually tries to grab the args from the BackStackEntry which you are scoping the VM to. The entry you are scoping it to is the graph, and not the specific destination. So it goes inside this function https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigati[…]y&amp;ss=androidx%2Fplatform%2Fframeworks%2Fsupport:navigation%2F and the SSH is here https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigati[…]y&amp;ss=androidx%2Fplatform%2Fframeworks%2Fsupport:navigation%2F In both cases the
args
is from the graph and not your specific screen as far as I can see
Since your VM will be alive for when those screens may also not be in the backstack, I don't see a way for the VM to have access to those at the time it's constructed. Is it not an option to just send those in from your screen whenever you need to? In your screen you can always grab the args from the current backstackEntry. With the new type-safe APIs you can just do
BackStackEntry.toRoute()
and you can get your whole route object in a safe way too. https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigati[…]java/androidx/navigation/NavBackStackEntry.kt;l=296-308;bpv=0
n
Since all the main logic is handled in VM, I thought it'll be easier if I access them using ssh, or else I would need to set them using route and LaunchedEffect and while remembering a key, which doesn't look that clean
s
If you did have access to them from the SSH, what would you expect to be the case, that if you are in that screen, the SSH gets access to those args so you can listen to it, and when you leave the screen they go back to null? If you do
val x = ssh[]....
it would anyway be null at initialization time. If you do this inside some function instead of at initialization, have that function receive the route's args instead.
n
While having this converstion, I have noticed I'll need the args only for
CustomerDataViewModel
where I am retrieving Data through API, I have in memory-cache in repo, so I was passing the userId as a key for it, I think i'll just won't share the other VM since it won't require any args, It'll work right ?
s
And all this does make me suspicious why you need the screen specific args in a VM scoped higher than it. I'd also consider the option of having the VM just be scoped to the screen it needs to grab the args from. Of course I know nothing about your use case, just throwing ideas that I've gone through in the past too.
I have in memory-cache in repo, so I was passing the userId as a key for it
Yeah then sounds like one of the two VMs don't need to be alive for more than the specific screen to me. Just have it scoped there, grab that ID from the screen args and have it load things from the SSOT which is the cache. Sounds like the most robust way to do this to me.
n
yep, I was doing the same, now it just need not to be a sharedViewModel, so that it can receive the args
Copy code
val customerDataState = customerRepository
    .getNewCustomerDetails(regNo)
    .map { it.toCustomerDataState() }
    .stateIn(viewModelScope, SharingStarted.Lazily, CustomerDataState())
Thanks!
🥳 1
s
Glad to have helped! Some unsolicited advice since you shared this last snippet, doing
SharingStarted.Lazily
in your
StateIn
will mean that if you have this destination in your backstack, and you continue moving forward, or even leave the app and it stays alive, this flow will stay alive and querying the
getNewCustomerDetails
the entire time. Even if this screen is like 10 levels behind in the backstack, it does not stop. Quite often you want the combo of
SharingStarted.WhileSubscribed()
+
collectAsStateWithLifecycle()
to avoid that.
n
Oh, Thanks, didn't knew about this
s
205 Views