How to navigate to first fragment without recreating the fragment - android

So example i have 3 fragment
fragment A with 1 edittext, and 1 button
fragment B with 1 textview, and 1 button
framgnet C with 2 textview and 1 button
fragment A with edittext data example "this is test" -> fragment B -> fragment C -> fragment A again
how to prevent fragment A recreate again when from fragment C, so the edittext input in fragment A not empty.
here 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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.fjr.simplenavigation.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"/>
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.fjr.simplenavigation.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" >
<action
android:id="#+id/action_secondFragment_to_thirdFragment"
app:destination="#id/thirdFragment" />
<argument
android:name="data"
app:argType="string"
app:nullable="true" />
</fragment>
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:destination="#id/firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
if i replace the code for action_thirdFragment_to_firstFragment to like this
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="false"/>
</fragment>
it will solve the problem, fragment A will not recreated and the edittext still have the input like on the description, but is there any other way?
the second question is how to passing the data back to fragment A (but with edittext still have an input like on the description)?

You can use either an Activity scoped or NavGraph scoped ViewModel, and store your variable in there.
class FragmentA : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
myEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
viewModel.text = s.toString()
}
})
}
}
class ViewModelA : ViewModel() {
...
var text: String = ""
}
In the above example, your ViewModel would live as long as your Activity lives. If you'd like to modify your ViewModel, you would reach it the same way in any other Fragment, and get the same instance of the ViewModelA
class FragmentC : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>() <-- gives you the same instance of the ViewModel
}
Read more about "Sharing data between Fragments" here:
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing

Related

Dynamically change startDestination of nested navigation graph inside of a BottomNavigationView

I'm trying to update my app to use BottomNavigationView. The first tab contains a HostFragment with a loading spinner that performs a network request to determine which fragment, either HomeFragment or LockedFragment, should be shown in that tab.
MainActivity handles the initial setup of the BottomNavigationView:
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_container
) as NavHostFragment
navController = navHostFragment.navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.mainFragment)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
My main nav graph looks like this:
<?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"
android:id="#+id/nav_graph"
app:startDestination="#+id/home">
<include app:graph="#navigation/home"/>
<include app:graph="#navigation/list"/>
<include app:graph="#navigation/form"/>
</navigation>
with the home graph looking 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"
android:id="#+id/home"
app:startDestination="#+id/hostFragment">
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment" />
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.android.bottomnav.homescreen.HomeFragment"
android:label="Home" />
<fragment
android:id="#+id/lockedFragment"
android:name="com.example.android.bottomnav.homescreen.LockedFragment"
android:label="Locked"/>
</navigation>
HostFragment get's shown fine and loads it's data:
class HostFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_host, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
determineFragmentToShow()
}
private fun determineFragmentToShow() {
lifecycleScope.launchWhenStarted {
// mock network call to determine tab
delay(1500)
// show HomeFragment for the sake of the example, but note that
// this would be dependent on the network call's result above
findNavController().navigate(R.id.action_hostFragment_to_homeFragment)
}
}
}
which successfully navigates us to HomeFragment.
Now the problem is that whenever I press the back button from HomeFragment it goes back to HostFragment instead of closing the app.You can see the behavior in this video here.
I tried to set the popUpTo and popUpInclusive tags inside of home.xml like this:
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
That got the app to close when pressing back from HomeFragment, but now each time I switch to a new tab, it creates a new instance of the fragment and adds it to the backstack. Pressing the back button then will traverse them all backwards. You can see that behavior in this video here.
So how can I update the start destination of a nested navigation graph?
I'm using the latest 2.4.0-alpha10 of navigation component so that I can get native support for multiple backstacks. Any help is greatly appreciated!
I was able to utilize the answer here to get a solution working for me.
Inside of determineFragmentToShow() in HostFragment, I just replaced findNavController().navigate(R.id.action_hostFragment_to_homeFragment) with
val navController = findNavController()
val graph = navController.graph
val walletGraph = graph.findNode(R.id.home) as NavGraph
walletGraph.setStartDestination(R.id.homeFragment)
navController.navigate(R.id.action_hostFragment_to_homeFragment)
I still needed to include the popUpTo and popUpInclusive tags here
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
but this got me the back behavior I was looking for!

OnBackPressed does not work correctly in some fragments

In MainActivity I have next method:
override fun onBackPressed() {
val onBackPressListener = currentFragmentOnTop() as? OnBackPressListener
if (onBackPressListener?.onBackPress() != true) super.onBackPressed()
}
But sometimes he doesn't work correctly.
For example:
In my startDestination fragment I call dialog(bottomMenuDialog) with menuItems, by clicking on which I call fragments:
findNavController().navigate(R.id.settings_list)
Here is code of my nav_graph.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#id/authorizationsListFragment">
<fragment
android:id="#+id/authorizationsListFragment"
android:name="com.mandarine.sai.features.authorizations.list.AuthorizationsListFragment"
android:label="AuthorizationsListFragment">
<action
android:id="#+id/settings_list"
app:destination="#+id/settingsListFragment" />
<action
android:id="#+id/connections_list"
app:destination="#+id/connectionsListFragment" />
<action
android:id="#+id/bottom_menu_dialog"
app:destination="#+id/bottomMenuDialog" />
</fragment>
But I can't click on the back button to go to the start fragment.
Behavior: it opens the start fragment and immediately goes back to the current one, to settings_list.
override fun onBackPressed() {
finish()
}

Too many arguments for public final fun actionConformationFragmentToHomeFragment()

I want to send my arguments to another fragment using NavDirections
<fragment
android:id="#+id/conformationFragment"
android:name="com.example.panoramic.ui.ConformationFragment"
android:label="ConformationFragment"
tools:layout="#layout/fragment_conformation">
<action
android:id="#+id/action_conformationFragment_to_registerProductFragment"
app:destination="#id/registerProductFragment" />
<action
android:id="#+id/action_conformationFragment_to_homeFragment"
app:destination="#id/homeFragment" />
<argument
android:name="modelNumber"
app:argType="string" />
<argument
android:name="serialNumber"
app:argType="string" />
</fragment>
Here is how I attempt to send arguments:
class ConformationFragment : Fragment(R.layout.fragment_conformation) {
private var fragmentConformationBinding: FragmentConformationBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentConformationBinding.bind(view)
fragmentConformationBinding = binding
val modelNumber = binding.modelValue.text.toString()
val serialNumber = binding.serialValue.text.toString()
binding.editingInformationButton.setOnClickListener {
findNavController().navigate(R.id.action_conformationFragment_to_registerProductFragment)
}
binding.confirmButton.setOnClickListener {
findNavController().navigate(ConformationFragmentDirections
.actionConformationFragmentToHomeFragment(modelNumber, serialNumber))
}
}
override fun onDestroyView() {
fragmentConformationBinding = null
super.onDestroyView()
}
}
I am sure dependencies are correct, but still, the error appears:
Too many arguments for public final fun actionConformationFragmentToHomeFragment(): NavDirections defined in com.example.panoramic.ui.ConformationFragmentDirections.Companion
and red line under modelNumber and serialNumber in actionConformationFragmentToHomeFragment() function
I checked other similar question but none of them works for me.
The issue is that the arguments you defined in your navigation graph file are accepted by ConformationFragment and not by HomeFragment.
Arguments should be defined under <fragment> tag of a fragment that is accepting defined arguments, so every other navigation action to this fragment would accept arguments. This is how the navigation graph should be declared regarding ConformatioFragment and HomeFragment:
<fragment
android:id="#+id/conformationFragment"
android:name="com.example.panoramic.ui.ConformationFragment"
android:label="ConformationFragment"
tools:layout="#layout/fragment_conformation">
<action
android:id="#+id/action_conformationFragment_to_registerProductFragment"
app:destination="#id/registerProductFragment" />
<action
android:id="#+id/action_conformationFragment_to_homeFragment"
app:destination="#id/homeFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.panoramic.ui.HomeFragment"
android:label="HomeFragment"
tools:layout="#layout/fragment_home">
<argument
android:name="modelNumber"
app:argType="string" />
<argument
android:name="serialNumber"
app:argType="string" />
</fragment>
After these changes, you should be able to pass arguments into actionConformationFragmentToHomeFragment() function.

Passing Multiple Agument to fragment using TypeSafe in Navigation

I know it is possible to pass a single argument using Action in the source fragment
override fun onClick(v: View) {
val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
val amount = amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
v.findNavController().navigate(action)
}
and get that in the destination fragment as specified in android docs
val args: ConfirmationFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val tv: TextView = view.findViewById(R.id.textViewAmount)
val amount = args.amount
tv.text = amount.toString()
}
please let me know is there any way to pass multiple arguments in TypeSafe way
Yes, you could do it by defining multiple arguments for your fragment in the Navigation graph and then pass them to the action in your code. This is an example:
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/navigation"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.example.app.FirstFragment"
android:label="FirstFragment"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"
app:enterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_left" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.example.app.SecondFragment"
android:label="secondFragment"
tools:layout="#layout/fragment_second">
<argument
android:name="firstDataList"
app:argType="com.example.app.domain.FirstData[]" />
<argument
android:name="secondDataList"
app:argType="com.example.app.domain.SecondData[]" />
<argument
android:name="isOkey"
app:argType="boolean" />
<argument
android:name="myString"
app:argType="string" />
</fragment>
</navigation>
and then in your code:
You should pass the arguments respectively as it is navigation.xml
FirstFragment.kt
findNavController().navigate(
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
firstDataList.toTypedArray(),
secondDataList.toTypedArray(),
isOkey,
myString
)
And then retrieve it at the destination as a bundle like so:
SecondFragment.kt
val args = arguments?.let {
SecondFragmentArgs.fromBundle(
it
)
}
if (args != null) {
firstDataList = args.firstDataList.toCollection(ArrayList())
secondDataList = args.secondDataList.toCollection(ArrayList())
isOkey = args.isOkey
myString = args.myString
}
To pass complex objects you should make them parcelable. In my example, I passed two lists of complex models that I parcelized them like this:
DataModels.kt
#Parcelize
data class FirstData(var id: Int, var color: Int = 0) : Parcelable
#Parcelize
data class SecondData(var name: String, var position: ArrayList<Int>) : Parcelable

New navigation component from arch with nested navigation graph

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

Categories

Resources