Feel like I'm getting a bit lost in all of the vie...
# compose-android
c
Feel like I'm getting a bit lost in all of the viewModel() apis. Would this be the right way to get a
NavHost
scoped
ViewModel
when using a
NavDrawer
?
Copy code
ModalNavigationDrawer(
    drawerContent = {
      val parentEntry = remember(navBackStackEntry) { 
navController.getBackStackEntry("parentNavigationRoute") 
}
      val parentViewModel: RootAppViewModel = hiltViewModel(parentEntry)
      
      NavDrawerContent(
          currentDestination,
          drawerState,
          buttonClick = { parentViewModel.doSomething() }
      )
    },
    content = {
      ModalBottomSheetLayout(bottomSheetNavigator) {
        NavHost(navController, startDestination = "home") {
Actually... that doesn't seem to work. Must have been running a previous piece of code. The above crashes on launch with
Copy code
java.lang.IllegalArgumentException: No destination with route parentNavigationRoute is on the NavController's back stack. The current destination is null
EDIT: Im trying to follow these docs btw: https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis#compose
s
You can name the root route in your NavHost, there's a
route
parameter on it afaik. Then get the backstack entry just as you're doing already. Then idk about the hilt api, but that probably looks correct
c
Okay. Updated the name of my navHost to
rootNavGraph
. Seems like some general race condition. where my navgraph doesn't actually exist yet.
java.lang.IllegalArgumentException: No destination with route rootNavGraph is on the NavController's back stack. The current destination is null
s
Oh wait, you're doing this outside of the NavHost itself, before it ever gets a chance to initialize itself then?
Or, you got two NavHosts? A bit confused, probably would be interesting to see more of the code.
c
I am doing it outside the navHost as I have a navDrawer that wraps my root NavHost, and I want a click of an item in NavDrawer to call a navHost's viewModel method.
So. basically i have a button in nav drawer and I want to call a VM method on click.
Current code. Notice 1 NavHost, wrapped with a NavDrawer
Copy code
@Composable
fun MyApp(
  appStateHolder: AppStateHolder
) {
  val bottomSheetNavigator = rememberBottomSheetNavigator()
  val navController = rememberNavController(bottomSheetNavigator)
  val navBackStackEntry by navController.currentBackStackEntryAsState()
  val currentDestination = navBackStackEntry?.destination

ModalNavigationDrawer(
    drawerContent = {
      val parentEntry = remember(navBackStackEntry) { 
navController.getBackStackEntry("rootNavGraph") 
}
      val parentViewModel: RootAppViewModel = hiltViewModel(parentEntry)
      
      NavDrawerContent(
          currentDestination,
          drawerState,
          buttonClick = { parentViewModel.doSomething() }
      )
    },
    content = {
      ModalBottomSheetLayout(bottomSheetNavigator) {
        NavHost(route = "rootNavGraph", navController = navController, startDestination = "home") {
            composable("home") { backStackEntry -> Demo1Screen() }
...
s
Right right, which means it may be doing all this too early
Do you need that to be in a ViewModel specifically? Anything that's outside of the NavHost can just be served with a simple MyAppState, exactly like NiaAppState does here https://github.com/android/nowinandroid/blob/main/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt
I do the same in our app and it works super well. Besides, why do you want to scope something to the root of your NavHost? It's the same as scoping it to your activity, aka not doing anything about scoping it. Their lifetime is exactly the same.
c
I guess I just want a VM that's scoped to the highest "composable" in the app (basically my root app) without the VM/scope being the activity.
s
What's the difference between those two though?
c
meaningfully, nothing. but im trying to keep this new project separated from "android" as much as possible. hence i dont want to be scoped to the "activity"
<=== I believe this would be "scoped" to the activity. And id like it to be scoped to my apps root composable instead
Copy code
fun MyApp(
  appStateHolder: AppStateHolder,
  activityViewModel: RootAppViewModel = hiltViewModel()
) {
s
"separated from android" Then what better thing than separating it completely by not even making it an Android VM in the first place?
c
touche!
thats a good point. i guess im asking for something that doesn't exist? a scope in between activity scope and the NavHost scope.
sorta related. is there an easy way to know what scope/`ViewModelStoreOwner`. my viewModel belongs to? Like in...
Copy code
fun MyApp(
  appStateHolder: AppStateHolder,
  activityViewModel: RootAppViewModel = hiltViewModel()
) {
im not sure if its actually scoping it to the Activity or NavGraph?
s
a scope in between activity scope and the NavHost scope.
Yeah there's nothing between there because those two already are equivalent, they have the same lifecycle.
For that you'd have to look into what hiltViewModel does, I don't use hilt so I can't say with high confidence. It probably looks at the CompositionLocal which holds a lifecycle owner of some sorts. On your activity that would be your activity. Inside androidx.navigation destinations that'd be the backstackentry itself. So if you're top level it just is your activity I'd say. You can look into the sources of what composable("route") {} does and see which locals it sets, and then see which ones hiltViewModel reads, there must be some overlap there.
c
Yeah there's nothing between there because those two already are equivalent, they have the same lifecycle.
wait. do they really? like. i know they have a similar lifecycle, but for the navHost is it really the activity lifecycle/scope/viewModelStoreOwner? hmm. might be a pain and ping Ian when he's back from vacay 😅
ill try to do some digging through the source. thanks for the conversation stylianos!
111 Views