Take a Bottom Navigation activity from android studio template. there are 3 fragment with 3 item in BottomNavBar (HomeFragment, DashboardFragment, NotificationsFragment) navigate to DashboardFragment from HomeFragment by a button click. after that home item click from BottomNavBar should open Homefragment. But not working as expected.
Go to DashboardFrament from HomeFrament by
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
navView.setupWithNavController(navController)
}
}
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFramgent.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val textView: TextView = view.findViewById(R.id.text_home)
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
}
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.app.bottomnav.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/navigation_dashboard"
android:name="com.app.bottomnav.ui.dashboard.DashboardFragment"
android:label="#string/title_dashboard"
tools:layout="#layout/fragment_dashboard" />
<fragment
android:id="#+id/navigation_notifications"
android:name="com.app.bottomnav.ui.notifications.NotificationsFragment"
android:label="#string/title_notifications"
tools:layout="#layout/fragment_notifications" />
</navigation>
Whenever you are navigating to any root destination from another fragment, you should have to clear the previous stack using popUpTo option builder.
Update your code navigation code in your home fragment.
findNavController()
.navigate(R.id.navigation_dashboard,
null,
NavOptions.Builder()
.setPopUpTo(R.id.navigation_home, true)
.build()
)
I don't know what is the best solution for that problem but here is a workaround:
In your activity you add public method:
fun navigateToNavBarDestination(destinationId: Int) {
binding.navView.setSelectedItemId(destinationId)
}
And in your fragments referenced in BottomNavigationView instead calling NavController::navigate you navigate in that way, lets say to dashboard:
fun navigateToDashboard() {
(activity as? MainActivity)?.navigateToNavBarDestination(R.id.dashboard)
}
Its not the perfect solution but works.
Add these lines in your MainActivity onCreate.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_dashboard,
R.id.navigation_notifications
))
setupActionBarWithNavController(navController, appBarConfiguration)
I am new to using the navigation component for Android. Most of the examples I have seen online discuss fragment navigation within an activity but, in my case, I want to navigate within a base fragment. In the picture below, I would like to navigate from a login fragment to a signup fragment within the same settings tab but am running into issues and have tried various possible solutions. Any help would be appreciated as I am aware that I can certainly missing something.
java.lang.IllegalArgumentException: Navigation action/destination com.example.animaljournalapp:id/action_userAccountFragment_to_userSignUpFragment cannot be found from the current destination Destination(com.example.animaljournalapp:id/navigation_settings) label=Settings class=com.example.animaljournalapp.ui.settings.UserAccountFragment
I attempt to perform the navigation in the login fragment file through a listener I have attached to a "Sign Up" button:
UserAccountFragment.kt
class UserAccountFragment : Fragment() {
private lateinit var userAccountModel: UserAccountModel
private lateinit var navHostFrag: Fragment
private var _binding: FragmentUserAccountBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
userAccountModel =
ViewModelProvider(this).get(UserAccountModel::class.java)
navHostFrag = SettingsMainFragment()
_binding = FragmentUserAccountBinding.inflate(inflater, container, false)
val root: View = binding.root
val loginButton: Button = root.findViewById(R.id.login_button)
val signupButton: Button = root.findViewById(R.id.signup_button)
loginButton.setOnClickListener{ view: View ->
Toast.makeText(this.context, "Hi", Toast.LENGTH_LONG).show()
}
signupButton.setOnClickListener { view: View ->
val action = UserAccountFragmentDirections.actionUserAccountFragmentToUserSignUpFragment()
Navigation.findNavController(view).navigate(action)
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
The main activity has a navigation graph of the following:
mobile_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.example.animaljournalapp.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/navigation_dashboard"
android:name="com.example.animaljournalapp.ui.dashboard.DashboardFragment"
android:label="#string/title_dashboard"
tools:layout="#layout/fragment_dashboard" />
<fragment
android:id="#+id/navigation_settings"
android:name="com.example.animaljournalapp.ui.settings.UserAccountFragment"
android:label="#string/title_settings"
tools:layout="#layout/fragment_settings_main" />
</navigation>
settings_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/settings_navigation"
app:startDestination="#id/userAccountFragment">
<fragment
android:id="#+id/userAccountFragment"
android:name="com.example.animaljournalapp.ui.settings.UserAccountFragment"
android:label="fragment_user_account"
tools:layout="#layout/fragment_user_account" >
<action
android:id="#+id/action_userAccountFragment_to_userSignUpFragment"
app:destination="#id/userSignUpFragment" />
</fragment>
<fragment
android:id="#+id/userSignUpFragment"
android:name="com.example.animaljournalapp.ui.settings.UserSignUpFragment"
android:label="UserSignUpFragment" />
</navigation>
fragment_settings_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.settings.SettingsMainFragment">
<fragment
android:id="#+id/settings_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/settings_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
I'm having an issue with a very simple task with Navigation component.
I have just 2 screens: MainFragment and SearchFragment.
When I try to go from MainFragment to SearchFragment by navigation with an action it works perfectly. Then I press the back button and naturally it goes back to the MainFragment.
The issue is, when I click the same button the second time to go again to the SearchFragment, I receive the following error:
java.lang.IllegalArgumentException: Navigation action/destination action_mainFragment_to_searchFragment cannot be found from the current destination Destination(searchFragment)
I'm navigating from the MainFragment to Search like this:
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
I tried redoing the nav_graph.xml but without success.
If I just navigate with the id directly, it works fine and I go back and forth as many times as I want
findNavController().navigate(R.id.searchFragment)
Any ideas how to fix the issue with the safe args?
Edit:
This is my nav_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph_main"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="news.presentation.main.MainFragment"
android:label="fragment_main"
tools:layout="#layout/fragment_main">
<action
android:id="#+id/action_mainFragment_to_searchFragment"
app:destination="#id/searchFragment" />
</fragment>
<fragment
android:id="#+id/searchFragment"
android:name="news.presentation.search.SearchFragment"
android:label="fragment_search"
tools:layout="#layout/fragment_search" />
</navigation>
This is the activity (it's basically just a container for the fragments):
This is the activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/homeFragContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph_main"
tools:context=".presentation.HomeActivity" />
And this is the HomeActivity.kt
#AndroidEntryPoint
class HomeActivity : AppCompatActivity(R.layout.activity_home)
This is the HomeFragment:
#AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
private val binding by viewBinding(FragmentMainBinding::bind)
private val viewModel by viewModels<MainViewModel>()
private val articlesAdapter = ArticlesAdapter(::onSubscriptionClicked)
private lateinit var layoutManager: LinearLayoutManager
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
setupViews()
setupViewModel()
setupRecyclerView()
}
private fun setupViews() {
binding.toolbar.title = getString(R.string.app_name)
binding.fab.setOnClickListener {
viewModel.intent.offer(MainViewModel.Intent.SearchClicked)
}
}
private fun setupViewModel() {
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
viewModel.feedFlow
.onEach(articlesAdapter::submitList)
.launchIn(lifecycleScope)
}
private fun setupRecyclerView() {
layoutManager = LinearLayoutManager(requireContext())
binding.recycler.layoutManager = layoutManager
binding.recycler.adapter = articlesAdapter
binding.recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val canScroll = recyclerView.canScrollVertically(-1)
binding.toolbar.isLifted = canScroll
}
})
}
private fun onSubscriptionClicked(article: Article) {
viewModel.intent.offer(MainViewModel.Intent.ItemClicked(article))
}
private fun handleState(state: MainViewModel.State) = when (state) {
NavigateToSearch -> findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
is FeedReady -> binding.progress.isVisible = false
Loading -> binding.progress.isVisible = true
is NavigateToArticle -> {
// works
// findNavController().navigate(
// R.id.articleFragment,
// bundleOf("articleLink" to state.link)
// )
}
}
}
And this is the XML for it:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
tools:context=".presentation.main.MainFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/bottomNav"
app:layout_constraintTop_toBottomOf="#id/toolbar"
tools:listitem="#layout/item_article_big" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/standard"
android:text="Add Feed"
android:textAlignment="center"
app:icon="#drawable/ic_add"
app:layout_constraintBottom_toTopOf="#id/bottomNav"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="#menu/menu_main" />
<tgo1014.news.presentation.customview.LiftableToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="#+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="#id/toolbar"
app:layout_constraintTop_toBottomOf="#id/toolbar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
the error is due to the lifecycle.
Replace this:
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
by
viewModel.state
.onEach(::handleState)
.launchIn(viewLifecycleOwner.lifecycleScope)
I am using jetpack navigation in two different activities each with its navigation host. I have Main activity which everything just works fine and Setup activity which have 2 fragments, one has a button which should navigate to other fragment when clicked .
The activity's code is as follows:
class SetupActivity : AppCompatActivity() {
private val navController by lazy { findNavController(R.id.nav_host_fragment) }
private lateinit var appBarConfig: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivitySetupBinding>(this, R.layout.activity_setup)
setSupportActionBar(toolbar)
appBarConfig = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfig)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfig) || super.onSupportNavigateUp()
}
}
Below is activity's xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".ui.SetupActivity"
tools:showIn="#layout/activity_setup">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/setup" />
</androidx.constraintlayout.widget.ConstraintLayout>
Below is code of first fragment which is also a start Destination
class ConfirmClassFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentConfirmClassBinding.inflate(inflater, container, false)
binding.fragment = this
return binding.root
}
fun navigate() {
toast("clicked") //this get called
val direction = ConfirmClassFragmentDirections.actionConfirmToSetup()
findNavController().navigate(direction) //THE PROBLEM IS HERE
//findNavController().navigate(R.id.setup)
}
}
I am using data binding to call fun navigate() as below:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="fragment"
type="com.nux.ui.fragments.ConfirmClassFragment" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="#dimen/spacing_middle">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{() -> fragment.navigate()}"
android:text="#string/continue_" />
</LinearLayout>
</layout>
and this how navigation xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/setup"
app:startDestination="#id/confirm">
<fragment
android:id="#+id/setup"
android:name="com.nux.ui.fragments.SetInfoFragment"
android:label="#string/setup"
tools:layout="#layout/fragment_set_info" />
<fragment
android:id="#+id/confirm"
android:name="com.nux.ui.fragments.ConfirmClassFragment"
android:label="#string/data_confirmation"
tools:layout="#layout/fragment_confirm_class">
<action
android:id="#+id/action_confirm_to_setup"
app:destination="#id/setup" />
</fragment>
</navigation>
When I click button fun gets called but nothing happens! What could be wrong?
From the code i can see you have the same id for 3 elements in the navigation graph.
#id/setup should only be used once and please change the name of the navigation graph from setup to something else.
I have one case and wish to implement it by arch navigation component. For example I have 2 Nav Graphs (main and nested). Can I call main graph from nested and how?
The point is to get the right NavController to navigate in the right graph.
Let's take this scenario as an example:
MainActivity
|- MainNavHost
|- NavBarFragment
| |- NestedNavHost
| | |-NestedContentFragment1
| | |-NestedContentFragment2
| |
| |- BottomNavigationView
|
|- LoginFragment
The main graph and the nested graph are in separate xml files: this is required, as far as I understood, because the navigations target different layout areas, so they require two different NavHosts. Each Navhost will need to reference its graph by id, which requires them to be in different resource files.
The point is that to navigate in a specific graph, we must get a reference to the right graph's owner: to do this, when calling Navigation.findNavController(view), the view argument is crucial.
Docs say that
NavHostFragments register their navigation controller at the root of their view subtree such that any descendant can obtain the controller instance through the Navigation helper class's methods
So for example, if inside NavBarFragment we write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navController = Navigation.findNavController(view)
}
here view is a parent of the NestedNavHost (that is the nested NavHostFragment), not a descendant, meaning that findNavController will search upstream in the tree and will return the MainNavHost's NavController.
If instead we write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.nestedNavHostFragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
}
where nestedNavHostFragment is the id of the FragmentContainerView in the layout, we get a reference to the correct NestedNavHost. Note the use of childFragmentManager, not parentFragmentManager.
In case you're still using the deprecated xml <fragment> tag, you can write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val fragmentContainer = view.findViewById<View>(R.id.nestedNavHostFragment)
navController = Navigation.findNavController(fragmentContainer)
}
where nestedNavHostFragment is the id of the <fragment> tag. We get a reference to the correct NestedNavHost now, because the view we pass to findNavController belongs to the NestedNavHost's subtree.
Similarly, if you need to get a reference to the main NavController from inside a NestedContentFragment, here's what we can do:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// we can get the innermost NavController using this view,
// because we are inside its subtree:
nestedNavController = Navigation.findNavController(view)
// we can find the outer NavController passing the owning Activity
// and the id of a view associated to that NavController,
// for example the NavHostFragment id:
mainNavController = Navigation.findNavController(activity!!, R.id.mainNavHostFragment)
}
Actually you could use Global actions to navigate from a nested nav graph destination to a main nav graph destination.
Create a global action from nested nav graph to desired destination in main nav graph (highlighted in the image below)
example:
<navigation android:id="#+id/main_nav_graph"
... >
<fragment android:id="#+id/fragStart" .../>
<fragment .../>
<fragment .../>
<navigation android:id="#+id/nested_nav_graph">
...
<!-- Global Action -->
<action
android:id="#+id/action_global_start"
app:destination="#id/fragStart" />
</navigation>
</navigation>
To navigate to main graph destination use
findNavController().navigate(R.id.action_global_start)
I created an answer with the info devrocca provided. It's a full answer from scratch, i didn't skip anything if anyone ever needs.
This is the main fragment for navigation. Camera is direct destination without any nested graph, Dashboard has it's own nested graph but it's added to same backstack camera fragment is added. Home has 3 fragments with it's own nav host
MainActivity
|- MainNavHost
|- HomeNavHostFragment
| |- NestedNavHost
| |-HomeFragment1
| |-HomeFragment2
| |-HomeFragment3
|
|- nav_graph_dashboard
|
|- CameraFragment
Here is the navigation files
Main Navigation nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/main_dest">
<!-- MainFragment-->
<fragment
android:id="#+id/main_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.MainFragment"
android:label="MainFragment"
tools:layout="#layout/fragment_main">
<!-- Camera -->
<action
android:id="#+id/action_main_dest_to_cameraFragment"
app:destination="#id/cameraFragment" />
<!-- Home NavGraph -->
<action
android:id="#+id/action_main_dest_to_nav_graph_home"
app:destination="#id/nav_graph_home" />
<!-- Dashboard NavGraph-->
<action
android:id="#+id/action_main_dest_to_nav_graph_dashboard"
app:destination="#id/nav_graph_dashboard" />
</fragment>
<!-- Camera -->
<fragment
android:id="#+id/cameraFragment"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.CameraFragment"
android:label="CameraFragment" />
<!-- Home-->
<include app:graph="#navigation/nav_graph_home" />
<!-- Dashboard-->
<include app:graph="#navigation/nav_graph_dashboard" />
<!-- Global Action Start -->
<action
android:id="#+id/action_global_start"
app:destination="#id/main_dest"
app:popUpTo="#id/main_dest"
app:popUpToInclusive="true" />
</navigation>
Dashboard nested navigation graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph_dashboard"
app:startDestination="#id/dashboard_dest">
<fragment
android:id="#+id/dashboard_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment1"
android:label="DashboardFragment1"
tools:layout="#layout/fragment_dashboard1">
<action
android:id="#+id/action_dashboardFragment1_to_dashboardFragment2"
app:destination="#id/dashboardFragment2" />
</fragment>
<fragment
android:id="#+id/dashboardFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment2"
android:label="DashboardFragment2"
tools:layout="#layout/fragment_dashboard2">
</fragment>
</navigation>
And nested navigation graph with it's own NavHost nav_graph_home
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph_home"
app:startDestination="#id/home_dest">
<fragment
android:id="#+id/home_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeNavHostFragment"
android:label="HomeHost"
tools:layout="#layout/fragment_home_navhost" />
<fragment
android:id="#+id/homeFragment1"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment1"
android:label="HomeFragment1"
tools:layout="#layout/fragment_home1">
<action
android:id="#+id/action_homeFragment1_to_homeFragment2"
app:destination="#id/homeFragment2" />
</fragment>
<fragment
android:id="#+id/homeFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment2"
android:label="HomeFragment2"
tools:layout="#layout/fragment_home2">
<action
android:id="#+id/action_homeFragment2_to_homeFragment3"
app:destination="#id/homeFragment3" />
</fragment>
<fragment
android:id="#+id/homeFragment3"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment3"
android:label="HomeFragment3"
tools:layout="#layout/fragment_home3" />
</navigation>
Layouts, i only add necessary ones, others are simple layouts with buttons, i add link for sample project with other navigation components samples included.
MainActivity
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.ActionBar" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/main_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Main Fragment, this is first fragment that shown in the image used as start of main navigation
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/btnDestCam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Destination Camera"
app:layout_constraintBottom_toTopOf="#+id/btnNavGraphHome"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/btnNavGraphHome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested NavHost Graph Home"
app:layout_constraintBottom_toTopOf="#+id/btnNavGraphDashboard"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnDestCam" />
<Button
android:id="#+id/btnNavGraphDashboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested Graph Dashboard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnNavGraphHome" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Layout that contains inner NavHostFragment for home navigation
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nested_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:defaultNavHost="false"
app:navGraph="#navigation/nav_graph_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity is for checking main navigation back stack, important thing here is
supportFragmentManager back stack is not updated as you navigate it's childFragmentManager even for main navigation, even if you only have one
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get NavHostFragment
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment)
// ChildFragmentManager of NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager.backStackEntryCount
val fragments = navHostChildFragmentManager.fragments
}
}
}
Fragment that contains Home navigation's host
class HomeNavHostFragment : BaseDataBindingFragment<FragmentHomeNavhostBinding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home_navhost
private var navController: NavController? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nestedNavHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
navController?.navigate(R.id.homeFragment1)
listenBackStack()
}
private fun listenBackStack() {
// Get NavHostFragment
val navHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment)
// ChildFragmentManager of the current NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
val fragments = navHostChildFragmentManager!!.fragments
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount, fragments: $fragments",
Toast.LENGTH_SHORT
).show()
}
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount",
Toast.LENGTH_SHORT
).show()
if (backStackEntryCount == 1) {
OnBackPressedCallback# this.isEnabled = false
requireActivity().onBackPressed()
} else {
navController?.navigateUp()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
}
There is one thing i don't know if it's improved in graph or code with nested NavHostFragment
If you set start destination of nav_graph_home HomeFragment1 instead of HomeNavHostFragment it works as dashboard which ignores nested NavHost and added to main back stack of fragments.
Since you are in inner NavHostFragment findNavController() in any home fragment returns the inner one
class HomeFragment3 : BaseDataBindingFragment<FragmentHome3Binding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home3
private var count = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dataBinding.btnIncrease.setOnClickListener {
dataBinding.tvTitle.text = "Count: ${count++}"
}
val mainNavController =
Navigation.findNavController(requireActivity(), R.id.main_nav_host_fragment)
dataBinding.btnGoToStart.setOnClickListener {
// 🔥Using destination belong to main_nav_host with nested navHost causes app to crash
// findNavController().navigate(R.id.action_global_start)
mainNavController.navigate(R.id.action_global_start)/**/
}
}
}
You can also use global action but it's not required since back navigation in inner navHost directly moves you back to main navigation if you don't use OnBackPressed.
Link for full example and the other nav component samples if you are interested.
Actually is working,
using
val host: NavHostFragment? = (childFragmentManager.findFragmentById(R.id.main_app_fragment_container) as NavHostFragment?)
I can navigate from main fragment
I found a temporary solution to the problem of inner NavController being covered.
You can use custom NavHostFragment which provides you with desired navController.
My code:
<androidx.fragment.app.FragmentContainerView
...
android:name="MyNavHostFragment"
app:defaultNavHost="false"
app:navGraph="#navigation/inner_nav">
...
</androidx.fragment.app.FragmentContainerView>
...
class MyNavHostFragment: NavHostFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainFragment.innerNavController = navController
}
}
...
class MainFragment : Fragment() {
companion object{
lateinit var innerNavController: NavController
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bottomNavigationView =
view!!.findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
bottomNavigationView.setupWithNavController(innerNavController)
}
}
we can achieve it by finding the root navhost controller and then navigating through root nav host controller
val Fragment.findRootNavHost: NavController?
get() = this.activity?.let {
Navigation.findNavController(it, your_root_fragment_id)
}
findRootNavHost?.navigate(`your_destination_fragment_id`)
Kindly check the medium article link
Github repo for the same