I'm creating an application where it uses the Navigation Architecture, but my navigation has no default host, and can be set based on the selected destination by the user.
So this is the setup of my NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var finalHost: NavHostFragment? = null
when (bundle) {
REQUEST_FIRST -> {
finalHost = NavHostFragment.create(R.navigation.navigation_first)
}
REQUEST_SECOND -> {
finalHost = NavHostFragment.create(R.navigation.navigation_second)
}
REQUEST_THIRD -> {
finalHost = NavHostFragment.create(R.navigation.navigation_third)
}
}
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentMain, finalHost!!)
.setPrimaryNavigationFragment(finalHost)
.commit()
}
and this is my main activity layout.
<?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:background="#color/black">
<!-- some stuff -->
<fragment
android:id="#+id/fragmentMain"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
So, my MainActivity is loading correctly but when I start to rotate the screen of the device, It encounters some errors.
java.lang.RuntimeException: Unable to start activity ComponentInfo: android.view.InflateException: Binary XML file line #10: Binary XML file line #10: Error inflating class fragment
the said error is pointing on my setContentView(R.layout.activity_main) and the NavHostFragment <fragment> in layout.
any help is appreciated, Thanks!
Related
I'm trying to set up a Wear OS project. I have some experience with normal android development but Wear os is new for me.
I'm used to just having one MainActivity and some fragments. But I'm trying to make that work for Wear OS but with no success. It keeps crashing when I want to use viewBinding in the OverviewFragment.kt.
I got the following project now:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.wear.widget.BoxInsetLayout 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:padding="#dimen/box_inset_layout_padding"
tools:context=".MainActivity"
tools:deviceIds="wear">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/inner_frame_layout_padding"
app:layout_boxedEdges="left|right">
<include
layout="#layout/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</androidx.wear.widget.BoxInsetLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.wear.widget.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<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_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
</androidx.wear.widget.BoxInsetLayout>
fragment_overview.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.OverviewFragment">
<TextView
android:id="#+id/tvOverview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:text="overview"
android:textAlignment="center" />
</FrameLayout>
MainActivity.kt
class MainActivity : FragmentActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
OverviewFragment.kt
class OverviewFragment : Fragment() {
private var _binding: FragmentOverviewBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_overview, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvOverview.setOnClickListener {
println("great")
}
}
}
Just a simple design, but it does not like the binding.tvOverview.setOnClickListener part in OverviewFragment.kt. It just keeps crashing.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.krakert.tracker, PID: 7261
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.krakert.tracker/com.krakert.tracker.MainActivity}: android.view.InflateException: Binary XML file line #17: Binary XML file line #8: Error inflating class fragment
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: android.view.InflateException: Binary XML file line #17: Binary XML file line #8: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class fragment
Caused by: java.lang.NullPointerException
at com.krakert.tracker.ui.OverviewFragment.getBinding(OverviewFragment.kt:15)
at com.krakert.tracker.ui.OverviewFragment.onViewCreated(OverviewFragment.kt:28)
I would like some help here, setting the project up. Really new to Wear OS
Thanks!
Not sure how this can work and suspect this is where the NullPointerException comes from
private var _binding: FragmentOverviewBinding? = null
private val binding get() = _binding!!
compare to the example at https://developer.android.com/topic/libraries/view-binding#fragments
which sets _binding in onCreateView
I have an application composed of one activity and several fragments, as recommanded by Google. Other details here. I would like to keep a menu still and to switch my fragments in the container in the center.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:someProperties="propertiesValues">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:someProperties="propertiesValues" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/navigation_map"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:someProperties="propertiesValues" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Activity creation")
val binding = ActivityMainBinding.inflate(layoutInflater)
println("Activity creation part 2")
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
User.initSharedPref(this)
}
Fragment
private lateinit var mylist: MutableList<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Fragment creation")
mylist = User.loadScenarioList()
}
User
object User
{
private lateinit var sharedPref : SharedPreferences
fun initSharedPref(context: Context){
sharedPref = context.getSharedPreferences("JeuDePisteKtPreferenceFileKey",Context.MODE_PRIVATE)
}
fun loadList(): MutableList<String>> {
val json = sharedPref.getString(KEY_LIST, "") ?: ""
if (json == "") return mutableListOf()
return Json.decodeFromString(json)
}
}
Problem encountered
When i start the activity, it initialize a variable sharedPref as shown in code.
But when in fragment onCreate i use this variable (mylist = User.loadScenarioList()), the binding line in activity fail with Binary XML file line #31: Error inflating class androidx.fragment.app.FragmentContainerView as shown in logcat below
Logcat & error
Here is the full logcat, we can see the the second sout is missing, but with no error thrown at this point.
The problem here came from the order of creation call
We can see it in the corrected code logcat
The activity onCreate is called first, but the first inflate call the fragment OnCreate, before resuming the activity OnCreate.
So every variable used in the fragment onCreate should be initialized before inflate call
I have a fragment which is a child of BottomsheetDialogFragment, inside that fragment I have a google map. At first when I open this fragment using arch components NavController it works fine, than if I close this fragment and than try to open it again it crashes.
This is the error log
Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #71 in com.example.com:layout/product_location_fragment: Error inflating class fragment
Caused by: java.lang.IllegalArgumentException: Binary XML file line #71: Duplicate id 0xffffffff, tag mapFragment, or parent id 0xffffffff with another fragment for com.google.android.gms.maps.SupportMapFragment
at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:116)
Here is the fragment itself
class ProductLocationFargment : BottomSheetDialogFragment, {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.product_location_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//this also always returns null
val frag = childFragmentManager.findFragmentById(R.id.mapFragment) as? SupportMapFragment
}
}
and layout 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">
<fragment
android:tag="mapFragment"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="#id/leftGuideView"
app:layout_constraintEnd_toEndOf="#id/rightGuideView" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is how I'm starting that fragment from another fragment
button.setOnClickListener {
val direction = AnotherFragmentDirections.actionAnotherFragmentToProductLocationFargment()
navController.navigate(direction)
}
Try adding the mapFragment to a FrameLayout manually instead of with <fragment tag, but only if if(savedInstanceState == null) { otherwise get it by tag
I was following the instructions on docs to use Android Jetpack preference library, but I am getting the following error
java.lang.RuntimeException: Unable to start activity ComponentInfo{ ... activity.SettingsActivity}: java.lang.IllegalArgumentException: No view found for id 0x7f090111 (... :id/settings_container) for fragment SettingsFragment
...
Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f090111 (... :id/settings_container) for fragment SettingsFragment
as I think this usually happens when there is no container to mount the fragment, but in my case there is a container(settings_container).
My code looks like below
SettingsActivity
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, SettingsFragment())
.commit()
}
}
SettingsFragment
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_main, rootKey)
}
activity_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.SettingsActivity">
</FrameLayout>
pref_main.xml (shortened)
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
app:key="data_sync_category"
app:title="#string/pref_data_sync_category">
<SwitchPreferenceCompat app:key="data_sync_status"
app:title="#string/pref_data_sync_status_title"
app:summary="#string/pref_data_sync_status_summary"
android:defaultValue="true"
/>
<ListPreference app:key="what_data_sync"
app:title="#string/pref_sync_frequency_title"
app:summary="#string/pref_sync_frequency_summary"
android:entries="#array/pref_sync_frequency_titles"
android:entryValues="#array/pref_sync_frequency_values"
android:defaultValue="30"
android:negativeButtonText="#null"
android:positiveButtonText="#null"
/>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>
As you can see code is almost identical to the code in documentation.
I tried invalidate and restart, did not work.
Please help
No, there isn't a container.
You never use setContentView() in your Activity, so that layout doesn't exist. tools:context is an IDE-only feature.
You need to use
setContentView(R.layout.activity_settings)
right after calling super.onCreate(savedInstanceState).
java.lang.RuntimeException: Unable to start activity ComponentInfo{ ... activity.SettingsActivity}: java.lang.IllegalArgumentException: No view found for id 0x7f090111 (... :id/settings_container) for fragment SettingsFragment
...
Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f090111 (... :id/settings_container) for fragment SettingsFragment
according your code you did't set layout for activity
you are giving reference of settings_container but its not found in activity layout because they dont have layout file so you need to setContentView for your activity
setContentView(R.layout.name)
public void setContentView (int layoutResID)
Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity.
Replace your activity kt file with
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, SettingsFragment())
.commit()
}
}
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