NavigationComponent: NavHostFragment inside a fragment - android

I have this tabbed UI with a navigation component and a BottomNavigationView that handles the tabs.
My use case involves one of these tab fragments to have a BottomNavigationView of its own.
I don't think the nested navigation graphs will work for me because I need there to be an inner NavHostFragment and a second BottomNavigationView.
So I'm in the fragment which I wish to host my inner navigation graph. It contains a fragment like so.
<androidx.fragment.app.FragmentContainerView
android:id="#+id/inner_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
I need a way to get the navigation controller for the above fragment.
Normally when you're in an activity you'd call the findFragmentById from the supportFragmentManager with the id you gave to your fragment.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.outer_nav_host) as NavHostFragment
So I tried to call that on the activity object from within my inner fragment
requireActivity().supportFragmentManager.findFragmentById(R.id.inner_host_fragment)
But the findFragmentById returns null.

Try to get the NavHostFragment from the supportFragmentManager, and then get the navController upon.
val navHostFragment =
requireActivity().supportFragmentManager.primaryNavigationFragment as NavHostFragment
val navController = navHostFragment.navController
Thanks to #ianhanniballake you need to use the childFragmentManager as your current fragment is a child of a fragment.
So try to use instead:
val navHostFragment =
childFragmentManager.primaryNavigationFragment as NavHostFragment
val navController = navHostFragment.navController
UPDATE:
throws this java.lang.NullPointerException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment
So, the solution as from comments:
val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_fragment)
as NavHostFragment
And replace nav_host_fragment with the id of the inner NavHostFragment.

Related

How to get navController inside activity?

I use Jetpack Navigation for navigating between fragments and activity. So, I have a MainActivity with a FragmentContainerView in the layout. I can easily navigate from fragment to fragment/activity. But, I can't find a way how to navigate from one activity to another activity/fragment with navController.
For example, from fragment FA, I call navController.navigate() to activity A. Now, I want to navigate from activity A to activity B or fragment FB.
I already tried this:
val host = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = host.navController
But always got this error
null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment
Thanks!
A note from the navigation component guide
Note: The Navigation component is designed for apps that have one main activity with multiple fragment destinations. The main activity is associated with a navigation graph and contains a NavHostFragment that is responsible for swapping destinations as needed. In an app with multiple activity destinations, each activity has its own navigation graph.
You shouldn't use the navigation component to navigate from one activity to another. It is made to swap fragments on a Fragment container.
You can get navController inside activity using
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
You also have to add android:name="androidx.navigation.fragment.NavHostFragment" to your fragment container
<androidx.fragment.app.FragmentContainerView
android:id="#+id/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"
/>
For java coder;
it is possible to get NavController instance this way
NavHostFragment navHostFragment =(NavHostFragment)getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();

Defining a NavHostFragment inside another fragment

I have a fragment that I define a NavHostFragment inside it like this:
<fragment
android:id="#+id/shipping_host_nav"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/shipping_nav_graph"
app:defaultNavHost="true"/>
when trying to call findNavController method in the fragment it threw an illegal state exception and says that my view group doesn't have a NavController.
java.lang.IllegalStateException: View androidx.core.widget.NestedScrollView{1dd5506 VFED..... ......I. 0,0-0,0} does not have a NavController set
So my question is: can I define a NavHostFragment inside another fragment?
or suitable for activity only?
I have searched a lot to find can I define a nav host fragment inside another fragment but I didn't find any answers.
I have found a solution for this exception, the findNavController() throws this exception when trying to call this method within a fragment that is not NavHostFragment or not within NavHostFragment so I made this mistake by calling this method in my fragment.
So I have to find the controller by myself using Navigation class
Navigation.findNavController(activity, R.id.my_nav_host_fragment)
this is how to find the NavHostFragment (NavController) defined within a fragment
I made an extension function for Fragment class so I can be easily find the nav controller using id
fun Fragment.getFragmentNavController(#IdRes id: Int) = activity?.let {
return#let Navigation.findNavController(it, id)
}

Accessing graph-scoped ViewModel of child NavHostFragment using by navGraphViewModels

I am using the Navigation Component of Android Jetpack (2.2.0-alpha01).
I wish to use a child NavHostFragment nested inside my main NavHostFragment, equipped with its own child nav graph. Please view the following image for context:
The child nav host is defined like this inside the fragment that is at the front of the MainNavHost's stack:
<fragment
android:id="#+id/childNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:navGraph="#navigation/child_graph" />
Inside the fragment that is at the front of the CHILD Nav Host Fragment, I am trying to get a ViewModel scoped to the R.navigation.child_graph by using the following code:
private val childGraphScopedViewModel: ChildGraphScopedViewModel by navGraphViewModels(R.navigation.child_graph) {
viewModelFactory
}
When accessing the childGraphScopedViewModel, I am getting a crash with the error message:
java.lang.IllegalArgumentException: No NavGraph with ID 2131689472 is on the NavController's back stack.
I believe the lazy init call by navGraphViewModel() is looking for the navgraph inside the mainGraph.
How can I access a child navHostFragment scoped ViewModel? Thank you for your time.
This works for me without defining aby custom ViewModelProvider (2.2.0):
private val viewModel: ChildGraphScopedViewModel by navGraphViewModels(R.id. child_graph)
An Easy mistake to make is to use R.navigation.child_graph (bad) instead of R.id.child_graph (good)
You can do that by providing the viewModelStore of child NavController
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
val childHostFragment = childFragmentManager
.findFragmentById(R.id.childNavHostFragment) as NavHostFragment
val childNavController = childHostFragment.navController
val childViewModel: ChildGraphScopedViewModel = ViewModelProvider(
childNavController.getViewModelStoreOwner(R.navigation.child_graph)
).get(ChildGraphScopedViewModel::class.java)
}
I wrote a Kotlin Extension for making it easier
inline fun <reified T: ViewModel> NavController.viewModel(#NavigationRes navGraphId: Int): T {
val storeOwner = getViewModelStoreOwner(navGraphId)
return ViewModelProvider(storeOwner)[T::class.java]
}
Usage
val viewModel = findNavController().viewModel(R.navigation.nav)
For some reason creating the nav graph and using include to nest it inside the main nav graph was not working for me. Probably it is a bug.
You can select all the fragments that need to be grouped together inside the nav graph and right-click->move to nested graph->new graph
now this will move the selected fragments to a nested graph inside the main nav graph like this:
<navigation app:startDestination="#id/homeFragment" ...>
<fragment android:id="#+id/homeFragment" .../>
<fragment android:id="#+id/productListFragment" .../>
<fragment android:id="#+id/productFragment" .../>
<fragment android:id="#+id/bargainFragment" .../>
<navigation
android:id="#+id/checkout_graph"
app:startDestination="#id/cartFragment">
<fragment android:id="#+id/orderSummaryFragment".../>
<fragment android:id="#+id/addressFragment" .../>
<fragment android:id="#+id/paymentFragment" .../>
<fragment android:id="#+id/cartFragment" .../>
</navigation>
</navigation>
Now, inside the fragments when you initialize the ViewModel do this
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
If you need to pass the viewmodel factory(may be for injecting the viewmodel) you can do it like this:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }

android architecture components : What is the alternative to fragment container in android architecture navigation?

I want to achieve the same result as this code, As I am using android navigation components what should I out in place of the fragment container as there is only NavHostFragment.
val newFragment = ExampleFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment)
transaction.addToBackStack(null)
transaction.commit()
Assuming you have a destination in your navigation graph that looks like:
<fragment
android:id="#+id/example"
android:name=".ExampleFragment"/>
You'd simply call navController.navigate(R.id.example), getting a reference to your NavController via one of the methods described in the documentation.

Navigation Component: pass safeArgs from activity to a fragment

I have two graphs, so the first graph move from one fragment to an activity passing safeArgs to the activity.
val action = MyFragmentDirections.actionMyActivity(arg1, arg2)
Navigation.findNavController(view).navigate(action)
Now in the second, I want to pass these arguments from MyActivity to a fragment which belongs to this activity.
I can get the args:
val args = MyActivity.fromBundle(intent.extras)
The problem is there is not a Directions file for this activity, so I can't pass the arguments.
Navigation 1.0.0-alpha07 fixed the feature request for passing arguments to the start destination of a graph.
To use this, you'd need to:
Remove the app:navGraph attribute from your NavHostFragment
Call findNavController(R.id.your_nav_host_fragment).setGraph(R.navigation.your_graph, intent.extras)
Using the R.id of your NavHostFragment and R.navigation that you previously had on your app:navGraph tag. By passing the arguments into the setGraph call, your starting destination will get the arguments directly, without calling navigate again (which would, by default, create a new instance of the destination on your back stack - not what you want).
I don't know if this is recommended, but it is working:
val args = MyActivity.fromBundle(intent.extras)
navController.navigate(R.id.myActivityFragment, args.toBundle())
It work for me
Remove the app:navGraph attribute from your NavHostFragment
<androidx.fragment.app.FragmentContainerView
android:id="#+id/video_view_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
/>
and call
navHostFragment = supportFragmentManager
.findFragmentById(R.id.video_view_fragment) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.video_navigation, intent.extras)

Categories

Resources