问题
I'm looking for a generic way to create my custom fragment with that has OnBackPressedCallback and viewModel that extends NavHostFragment using navigation graph i intend to put as child fragments into it's back stack.
Normally i create NavHostFragment for each tab or fragment with their FragmentContainerView
, it's easy but repetitive to create for each host with
fragment_nav_host_home.xml
<?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_home"
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>
and writing databinding for their layouts, setting and using id nested_nav_host_fragment_home
class HomeNavHostFragment : BaseDataBindingFragment<FragmentNavhostHomeBinding>() {
override fun getLayoutRes(): Int = R.layout.fragment_navhost_home
private val appbarViewModel by activityViewModels<AppbarViewModel>()
private var navController: NavController? = null
private val nestedNavHostFragmentId = R.id.nested_nav_host_fragment_home
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nestedNavHostFragment =
childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment
navController = nestedNavHostFragment?.navController
// Listen on back press
listenOnBackPressed()
}
private fun listenOnBackPressed() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
override fun onResume() {
super.onResume()
callback.isEnabled = true
// Set this navController as ViewModel's navController
appbarViewModel.currentNavController.value = navController
}
override fun onPause() {
super.onPause()
callback.isEnabled = false
}
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
// Check if it's the root of nested fragments in this navhost
if (navController?.currentDestination?.id == navController?.graph?.startDestination) {
/*
Disable this callback because calls OnBackPressedDispatcher
gets invoked calls this callback gets stuck in a loop
*/
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else {
navController?.navigateUp()
}
}
}
}
Instead i tried to write more generic class for creating NavHostFragment
directly instead of putting it inside another fragment
class BaseNavHostFragment private constructor() : NavHostFragment() {
private val appbarViewModel by activityViewModels<AppbarViewModel>()
companion object {
fun create(
@NavigationRes navGraphId: Int,
startDestinationArgs: Bundle? = null
): BaseNavHostFragment {
return NavHostFragment.create(navGraphId, startDestinationArgs) as BaseNavHostFragment
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listenOnBackPressed()
}
private fun listenOnBackPressed() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
override fun onResume() {
super.onResume()
callback.isEnabled = true
// Set this navController as ViewModel's navController
appbarViewModel.currentNavController.value = navController
}
override fun onPause() {
super.onPause()
callback.isEnabled = false
}
/**
* This callback should be created with Disabled because on rotation ViewPager creates
* NavHost fragments that are not on screen, destroys them afterwards but it might take
* up to 5 seconds.
*
* ### Note: During that interval touching back button sometimes call incorrect [OnBackPressedCallback.handleOnBackPressed] instead of this one if callback is **ENABLED**
*/
private val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
// Check if it's the root of nested fragments in this nav host
if (navController?.currentDestination?.id == navController?.graph?.startDestination) {
/*
Disable this callback because calls OnBackPressedDispatcher
gets invoked calls this callback gets stuck in a loop
*/
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else {
navController?.navigateUp()
}
}
}
}
When i call create
function it returns ClassCastException
. Any way to create a NavHostFragment only passing R.navitation.x
is simple way for creating to add or replace with fragment manager or ViewPager2, but couldn't find how to create a fragment this way.
来源:https://stackoverflow.com/questions/62627332/how-to-create-a-custom-fragment-that-extends-navhostfragment-with-its-own-back