Thanks to a bottomNavigationView, I use NavController to navigate between 4 different fragments.
In one of these fragments (ChallengesFragment) I have an inner_fragment (nav_host_fragment_challenges) but when I try to navigate in this inner_fragment it doesn't work.
Here's my 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 navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.navigation_bar)
bottomNavigationView.setupWithNavController(navController)
}
}
My ChallengesFragment.kt (fragment that host the inner_fragment) :
class ChallengesFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val inflater_challenges = LayoutInflater.from(ContextThemeWrapper(context, R.style.Theme_challenges))
val view = inflater_challenges.inflate(R.layout.fragment_challenges, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val challengesNavHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_fragment_challenges) as NavHostFragment
//val challengesNavHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.nav_host_fragment_challenges) as NavHostFragment
val challengesNavController = challengesNavHostFragment.navController
}
}
My problem : val challengesNavHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_fragment_challenges) as NavHostFragment
give me this error: java.lang.NullPointerException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment
So I tried val challengesNavHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.nav_host_fragment_challenges) as NavHostFragment
(line with // in the code) and it works at first but when I leave ChallengesFragment and then come back the inner_fragment doesn't show up anymore...
I think the way to go is with childFragmentManager but if you have a solution that works with something else I take it.
The FragmentContainerView of my fragment_challenges.xml :
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_challenges"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/challenge_topbar"
app:navGraph="@navigation/nav_graph_challenges"
app:defaultNavHost="false"
tools:layout="@layout/fragment_quests" />
My nav_graph.xml if needed :
<?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/homeFragment">
<fragment
android:id="@+id/profileFragment"
android:name="fr.apgames.veuja.fragments.ProfileFragment"
android:label="ProfileFragment" />
<fragment
android:id="@+id/calendarFragment"
android:name="fr.apgames.veuja.fragments.CalendarFragment"
android:label="CalendarFragment" />
<fragment
android:id="@+id/challengesFragment"
android:name="fr.apgames.veuja.fragments.ChallengesFragment"
android:label="ChallengesFragment" />
<fragment
android:id="@+id/homeFragment"
android:name="fr.apgames.veuja.fragments.HomeFragment"
android:label="HomeFragment" />
<include app:graph="@navigation/nav_graph_challenges" />
</navigation>
And finally my nav_graph_challenges.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"
android:id="@+id/nav_graph_challenges"
app:startDestination="@id/challengesQuestsFragment">
<fragment
android:id="@+id/challengesMyStoryFragment"
android:name="fr.apgames.veuja.fragments.ChallengesMyStoryFragment"
android:label="ChallengesMyStoryFragment" >
<action
android:id="@+id/action_challengesMyStoryFragment_to_challengesQuestsFragment"
app:destination="@id/challengesQuestsFragment" />
</fragment>
<fragment
android:id="@+id/challengesQuestsFragment"
android:name="fr.apgames.veuja.fragments.ChallengesQuestsFragment"
android:label="ChallengesQuestsFragment" >
<action
android:id="@+id/action_challengesQuestsFragment_to_challengesMyStoryFragment"
app:destination="@id/challengesMyStoryFragment" />
</fragment>
</navigation>
The inflater
passed into onCreateView
not the same LayoutInflater
you would get from LayoutInflater.from(context)
- it is aware of what Fragment you are in. That's how the FragmentContainerView
knows that it needs to be added as a child fragment of the Fragment you're in (if you look at the source code, you'll see the special casing for FragmentContainerView
).
So when you use
val inflater_challenges = LayoutInflater.from(
ContextThemeWrapper(context, R.style.Theme_challenges))
You are creating a LayoutInflater that is completely unaware of what fragment you are on. This is why your fragment (which should be a child fragment) actually gets added to the Activity's FragmentManager
. That has the side effect of not actually nesting the fragments within one another, thus causing the fragment to not re-appear when the parent becomes visible again (because from FragmentManager's perspective, they are completely independent fragments).
This kind of issue would fire a WrongNestedHierarchyViolation
if you enabled StrictMode for fragments specifically because of the negative side effects of not nesting your fragments correctly.
So you should:
inflater
passed to you by onCreateView
android:theme="@style/Theme.challenges"
in your fragment_challenges.xml
XML file to set the theme on everything in that part of the hierarchy, rather than using ContextThemeWrapper
.