Is compose navigation ready?
# compose
k
Is compose navigation ready?
i
#soon
❤️ 8
Lots more to do after the first alpha though 😅
😂 2
👍 1
a
@Ian Lake I'm playing with it right now, does it support sending data to a navigation destination yet? Just asking.
i
a
Nice. Thanks!!
a
hi @Afzal Najam, how were you able to try it? I would love to try it myself as well. 😄
a
@Archie Yup! This should help you import it and then check out the sample on the androidx GitHub.
Copy code
maven { url = '<https://androidx.dev/snapshots/builds/6893483/artifacts/ui/repository>' }
Copy code
implementation "androidx.compose.navigation:navigation:1.0.0-SNAPSHOT"
❤️ 2
a
awesome thank you very much 😄
Hi @Ian Lake, in the current navigation component in android we have
Copy code
navController.addOnDestinationChangedListener { 
    ...
    // Hide/show shared UI here
    ...
}
We usually do this for switching persistent
AppBar
or any shared UI across screens. Is this still the case for
Compose
or will Compose Navigation Component introduce something different?
a
There's a
currentBackstackEntryAsState
function or something like that. Since we don't have listeners or classes much in the UI anymore, a state would be a compose-like approach. I'm guessing that all the rest of the functionality will get polished over time according to usage.
😮 1
a
I see... 🤔
i
Right, you just recompose your app bar, bottom nav, etc. as the current destination changes. "Hiding" is just not emitting that Composable
a
@Ian Lake Does that mean having the
AppBar
or any "shared UI across screen" be defined inside each destination like so?
Copy code
NavHost(startDestination = "Start") {
    composable("Start") {
        Scaffold(
            topBar = { MyCustomerAppBar() }
        ) {
            .... 
        }
    }
    composable("Screen2") {
        Scaffold(
            topBar = { MyCustomerAppBar() }
        ) {
            .... 
        }
    }
    composable("Screen3") {
        Scaffold(
            topBar = { DifferentAppBar() }
        ) {
            .... 
        }
    }
}
i
No, that's not sharing any UI at all between destinations. Just like other composables, it is about hoisting state (in this case, the
NavController
) to outside of the `NavHost`:
Copy code
val navController = rememberNavController()
val currentBackStackEntry = navController.currentBackStackEntryAsState()
Scaffold(
  topBar = {
    // Use whatever logic you want here
    if (currentBackStackEntry?.value?.arguments?.getBoolean("showAppBar") == true) {
      MyCustomerAppBar()
    } else {
      DifferentAppBar()
    }
) {
  NavHost(navController, startDestination = "start") {
    composable("start") {
    }
  }
}
❤️ 1
a
Thank you very much
Hi @Ian Lake may I ask another question again please. So in this setup:
Copy code
val navController = rememberNavController()
val currentBackStackEntry = navController.currentBackStackEntryAsState()
Scaffold(
  topBar = {
    // Use whatever logic you want here
    if (currentBackStackEntry?.value?.arguments?.getBoolean("showAppBar") == true) {
      MyCustomerAppBar()
    } else {
      DifferentAppBar()
    }
) {
  NavHost(navController, startDestination = "start") {
    composable("start") {
    }
  }
}
The app bar is shared across screens. Assuming that the app bar have app bar menu and when clicked, depending on the current screen it triggers a different action. In the current way of doing this with fragments, we do something like:
Copy code
class MyFragment : Fragment() {
    override fun onCreate(context: Context) {
        ....
        setHasOptionsMenu(true)
        ....
    }

    // To set the menu
    override fun onCreateOptionsMenu(menu: Menu) {
        ....
        menuInflater.inflate(R.menu.myMenu, menu)
        ....
    }

    // to Recieve the event when the menu is clicked
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handler action when menu item is clicked
    }

    ....
}
How should the same thing be handled in compose? Since the AppBar is now in the scope of the current View. The only way I can think of is doing something like this in compose.
Copy code
val navController = rememberNavController()
val currentBackStackEntry = navController.currentBackStackEntryAsState()

// create a state which I could pass down and listen to when value gets set
val someState = remember { SomeState() }
Scaffold(
  topBar = {
    Button(
       onClick = { someState.value  = SomeValue() }, // Set the value of the state 
       content = { Text("Click ME!")  },
    )
) {
  NavHost(navController, startDestination = "start") {
    composable("start") {
        MyScreen(someState) // Check value of someState inside My Screen to know when the button is clicked.
    }
  }
}
But I feel like this is the wrong way.
i
Think about what is actually happening under the hood in the old world: you have an activity scoped menu (a hoisted list of menu items in the Compose world), then each destination is adding elements to that list when it is added (committed in the Compose world) and removing them when it is removed (disposed in the Compose world).
❤️ 1
a
Aha! YESS! thank you! that makes so much sense... I apologize for not getting it sooner... Thank you very much! ❤️
@Ian Lake Last question, So I noticed by doing
viewModel()
inside a
composable(...) {}
the
ViewModel
is now scoped to that
composable(...)
instead of the
Activity
. as seen in this code snippet inside
NavHost
Copy code
...
if (destination is ComposeNavigator.Destination) {
    // while in the scope of the composable, we provide the navBackStackEntry as the
    // ViewModelStoreOwner and LifecycleOwner
    Providers(
         AmbientNavController provides navController,
         ViewModelStoreOwnerAmbient provides currentNavBackStackEntry!!,
         LifecycleOwnerAmbient provides currentNavBackStackEntry!!,
         children = destination.content
    )
}
....
I was wondering If is also possible to scope a
ViewModel
across multiple
composable(...)
just like how
navGraphViewModels(...)
work?
i
viewModel()
isn't doing anything special, just calling through to
ViewModelProvider
with the right
ViewModelStoreOwner
. Which is the same thing
navGraphViewModels()
does: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt;l=56
a
I think I get it now.. so basically calling
viewModel()
across
composable(...){...}
will still give the same
ViewModel
since its using the current
backStackEntry.viewModelStore
. did i get it right?
i
That is correct. If you want something other than that, you'd use
ViewModelProvider
directly
👍 1
a
Alright! Thank you very much! (and thank you for being very patient as well) ❤️