I am a bit confused on how the Navigation component fits in the app behavior. It all looks nice and shiny in tutorials where you don't do things too complex but when implementing in real app, things seem different.
Before Navigation
Before implementing navigation I had to manually run fragment transactions. In order to do this, my fragment would implement an interface onFragmentAction which passed a bundle to the main Activity and in the activity based on the actions, replace the current fragment with another one.
The second part that needs handling is the top toolbar and the BottomAppBar. For instance BottomAppBar needs to have the FAB aligned differently on some fragments or hidden in others. Also the top ToolBar needs to be expanded on some or collapsed on others. To do this, I listened to FragmentManager.OnBackStackChangedListener and based on the fragment tag getSupportFragmentManager().getBackStackEntryAt(size - 1).getName() change the layout accordingly.
With Navigation
The first part seems to be easy to do: pass params and start new fragments. But I have no idea if navigation can handle the toolbars management or I need to keep managing it from my Activity.
Even though Alex's solution works, I would not recommend it for the purpose of managing the toolbar.
The toolbar should be part of your fragment's layout and each fragment should manage its own toolbar. you can inflate different menus for each fragment. even in the case of wanting to have the toolbar in the activity, I would recommend getting a reference to the toolbar form activity (through an interface) and then adding and manipulating its items in the fragment itself.
This would decouple your activity and fragment (which is one of the goals of having navigation graph and a router). a good rule of thumb is that imagine you want to remove the fragment, then you should not need to make any change to the activity.
The toolbar title is set based on 'label' value inside navigation graph, if you want to do something different with toolbar or BottomAppBar you can add addOnNavigatedListener inside your activity, and based on current destination do something.
findNavController(nav_host_fragment).addOnNavigatedListener { controller,
destination ->
when(destination.id) {
R.id.destination1 -> {
//Do something with your toolbar or BottomAppBar
}
R.id.destination2 -> {
//Do something with your toolbar or BottomAppBar
}
}
}
In your fragment:
NavController navHostFragment = NavHostFragment.findNavController(this);
NavigationUI.setupWithNavController(toolbar, navHostFragment);
When I click item on list item (Explore Fragment) it will negation to DetailFragment and when I click the back button on the toolbar it will return MainFragment.
if you want to reach to another fragment by calling on menu item, you must give to item id the the same id which is in the destination id.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return
item.onNavDestinationSelected(findNavController(R.id.nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
<item android:id="#+id/dailyInfoFragment"
android:title="#string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<fragment
android:id="#+id/dailyInfoFragment"
android:name="com.example.sonyadmin.infoPerDay.DailyInfoFragment"
android:label="fragment_daily_info"
tools:layout="#layout/fragment_daily_info"
/>
Related
How can I setupWithNavController my BottomNavigationView with a FloatingActionButton (FAB) on top?
Thanks StackOverflow for hating me and not letting me display photos. :)
According to the Navigation library releases…
Starting in Navigation 2.4.0-alpha01, the state of each menu item is
saved and restored when you use setupWithNavController.
This works like a charm, however, when I setup my BottomNavigationView and press the FAB, the states of the current menu items are reloaded. How can I achieve the same behavior of clicking on any menu item with the FAB?
binding.activityMainBottomNavigationView.setupWithNavController(navController)
binding.activityMainFloatingActionButton.setOnClickListener {
// TODO: Fix navigation.
navController.navigate(R.id.homeFragment)
binding.activityMainBottomNavigationView.selectedItemId = R.id.homeFragment
}
As I understand, calling navController.navigate() may be resetting the current nav_graph used by the fragmentContainerView and that's why the current menu items are being reloaded. Is there any way to avoid this?
After a week of several attempts, I achieved the desired purpose using onNavDestinationSelected from NavigationUI.
binding.activityMainBottomNavigationView.setupWithNavController(navController)
binding.activityMainFloatingActionButton.setOnClickListener {
NavigationUI.onNavDestinationSelected(
binding.activityMainBottomNavigationView.menu.findItem(R.id.homeFragment),
navController
)
binding.activityMainBottomNavigationView.selectedItemId = R.id.homeFragment
}
In this way, the states of the fragments and navigation survive! 🤠
Cheers! 🍾
I want to achieve something like this image flow in android navigation component. Where the Dashboard Fragment is the start destination. And from here i can navigate to another fragment which have a bottom navigation view. Is this possible using a single nav graph and a single activity? What is the best way to achieve something like this?
You can use BottomNavigationView
val childNavView: BottomNavigationView = binding.nav1View
val navController = NavHostFragment.findNavController(childFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment)
childNavView.setupWithNavController(navController)
In case someone's still looking for a solution.
Just use two separate navigation graphs. And when navigating, pick the right navigation controller object. You can use a single activity of course, with nested fragments. So your "Bottom nav page" will have its own navigation container with child fragments in it.
Moreover, you can also navigate from the inner nodes to outer ones. In that case you need to reference the parent fragment ("Bottom nav page") from the child fragment and then obtain its navigation controller like this:
parentFragment?.parentFragment?.findNavController()
Here the first parentFragment is NavHostFragment and its parent is your actual parent fragment ("Bottom nav page").
If you want Page 1-3 to have options for further fragments to navigate inside (i.e. Page 1.1, 1.2 for Page 1, etc), then you need a navgraph for each Page (so 3 total), I would also make them all the root of their own navigation, would make it a lot easier to implement, and just replace the dashboard fragment with an activity if it's for signing in for example.
I have a navigation drawer with a navigation view with menu items and I'm using navigation component. How do I specify an argument for a destination at runtime when a certain menu item in the navigation view is clicked?
I know I can set default arguments in the nav graph but this is not what I want to do because the argument passed needs to be decided at runtime. For toolbar menu items, I have no issue as I'm able to override onOptionsItemSelected. My issue is specifically with menu items in the navigation view.
I have tried the approach here: https://stackoverflow.com/a/54086631/10933532 but I am unable to create a NavArgument as described. The NavArgument class does not give me the public Builder() constructor.
I set up my navigation view with the navigation controller in mainactivity onCreate like this:
NavigationUI.setupWithNavController(navigationView,navController);
I also do this to set up my toolbar with the navigation controller:
NavigationUI.setupWithNavController(collapsingToolbarLayout,toolbar,navController,appBarConfiguration);
Can someone please point me in the right direction?
You can use a custom NavigationItemSelectedListener.
Pay attention because in this way you are removing the original listener. You have to call NavigationUI.onNavDestinationSelected() to trigger the default behavior.
navigationView.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.xxxxx -> {
// handle click
true
}
}
//Call the original listener
NavigationUI.onNavDestinationSelected(it, navController)
drawerLayout.closeDrawer(GravityCompat.START)
true
}
I have a single Activity application. I created a single Navigation graph containing all the possible destinations (Fragment).
The Activity layout only contains a <fragment/> container as it should be.
There is a main Fragment containing a BottomNavigationView where the user can navigate to all the important destinations (3 of them).
When I implement the BottomNavigationView as described in many tutorials or event in the official documentation, it replaces the full content of the Activity. Therefore, the BottomNavigationView is no more displayed as it is contained in a Fragment and not in the Activity. I want it to inflate the right layout in the main Fragment.
Should I use another Navigation graph, specific for the main Fragment ?
Should I use a classic Listener (But I will loose all the interest of using a Navigation graph) ?
Is there a solution I don't know ?
How do I implement it ? I tried to create two different Navigation graph, but I can not set it to my BottomNavigationView
Answer:
Create a second Navigation graph containing the destination you want your BottomNavigationView to access.
In the fragment containing the BottomNavigationView make sure to insert a FragmentContainerView and an appropriate ID.
Then, thanks to This answer right here you can implement it easily.
Juste refer to the nested NavHost using this:
val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.bottom_nav_host) as? NavHostFragment
val navController = nestedNavHostFragment?.navController
val bottomNavigationView = view.findViewById<BottomNavigationView>(R.id.bottom_navigation)
if (navController != null) {
bottomNavigationView.setupWithNavController(navController)
} else {
throw RuntimeException("Controller not found")
}
I have an activity with BottomNavigationBar.
I show app logo in middle in toolbar by default.
Now I have to show SearchBar on entire toolbar when one of the bottomNavigation item is selected. Also, I want to revert to default toolbar view (one with logo in middle) on selecting any other bottomNavigation item.
How can i do this with Navigation component?
If I have to use ViewSwitcher or ActionMode, the whole idea of navigation component will have to be dropped as I can handle a few fragment transaction by myself.
Help me out here.
There's two approaches to do this:
1) Have each Fragment implement its own Toolbar
This approach gives you the ultimate flexibility in what each Fragment is responsible for, but is more suitable if you have many different types of Fragments or need scrolling behavior that is different for each Fragment.
2) Use a OnNavigatedListener to change your Activity's Toolbar
The NavController allows you to attach any number of OnNavigatedListener instances, which give you a callback whenever the current destination / item changes.
This allows you to write code in your Activity such as:
navController.addOnNavigatedListener { navController, destination ->
if (destination.id == R.id.search_destination) {
// Update your Toolbar to be a SearchBar
} else {
// Reset it back to a standard Toolbar
}
}