When I'm entering MessageDetails fragment, leaving with back arrow and navigating to other fragment, getting back and trying to click on button to MessageDetails again I got crash and error:
"Fragment not associated with a fragment manager".
fun handleEvent(event: MessageListEvent) {
when (event) {
is NavToMessageDetails -> {
val action =
MessageListFragmentDirections.actionMessageListFragmentToMessageDetailsFragment(
event.id,
DateHelper.parseFullDate(event.date),
event.message,
event.title
)
findNavController().navigate(action)
}
}
This is the fragment function which is responsible for navigating to the details fragment. In app we have created BottomNavigation with app:menu values and this error occurs when navigating between those fragments.
Event (MessageListEvent) was logged right under function declaration and everything is alright with it. Also, when I'm trying to wrap findNavController() with lifecycleScope.launchWhenResumed {} it won't navigate or do anything with it.
Related
I have this code in my Activity:
binding.imageViewDot.setOnClickListener {
findNavController(this, R.id.navHostFragmentApp).navigate(R.id.syncFragment)
}
}
When clicking the button it navigates to this fragment. But each time the user click it it adds another one. How can I set it to only once. For example, if fragment is visible do not run this code. For example:
binding.imageViewDot.setOnClickListener {
if (//fragment not visible)
{
findNavController(this, R.id.navHostFragmentApp).navigate(R.id.syncFragment)
}
}
You can use currentDestination to get the visible Fragment and check it's id before navigating
val navController = findNavController(this, R.id.navHostFragmentApp)
if(navController.currentDestination?.getId() != R.id.syncFragment){
navController.navigate(R.id.syncFragment)
}
I have an activity with two fragments. Either fragment is located on container in activity. Also activity has toolbar with settings button. I should pass on settings fragment using Navigation by click on settings button, but i have problems with it, because findNavController(R.id.home_nav).navigate(R.id.action_second_fragment_to_settings_fragment)
works if we pass to settings screen and second fragment is active, but from first active will be crash.
What do i need to do for solving this problem? I used just actions in Navigation component for passing between fragment. May be there is useful solution for this task
if your navController in MainActivity, with this code you can access to navController and send data to fragment.
load new fragment :
val bundle = Bundle()
bundle.putString("id", id)
(activity as MainActivity).navController.navigate(R.id.orderDetailFragment, bundle)
for get data in onViewCreated in fragment :
arguments?.let {
if (it.getSerializable("id") != null) {
val id = it.getString("id")
}
}
i hope it's useful
I'm using navigation components, I have a graph from where I have 3 destinations
FragmentA() -> FragmentB() -> FragmentC()
When I go from FragmentA() to FragmentB() , when pressing the backButton I dont want to come again to FragmentA(), instead I want to pop to FragmentC(), I have set popUpInclusive to true in the action that navigates from FragmentA() to FragmentB() and set popUpTo FragmentC() but when I press backButton on FragmentB() it stills going to FragmentA()
Whats wrong here ?
Solved it by adding a listener into the host activity and pop from there the backstack
override fun onBackPressed() {
when(navController.currentDestination?.id){
R.id.navigation_success -> {
navController.popBackStack(R.id.navigation_success,true)
navController.navigate(R.id.navigation_orders)
}
else -> navController.navigateUp()
}
}
There is a difference between navigateUp and onBackPressed, this two work in different ways, if we use navigateUp we need to touche the navigation graph and from there we can set popupTo and also popupInclusive, but if we are using just the onBackPressed button we need to attach this listener in our main host activity
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
I have implemented a bottomNavigationView which each option has its entry fragment and some has more navigation under the same option.
Somehow like this:
A->A1
B->B1
C->C1->C2
D->D1
E->E1->E2
Where A,B,C,D & E are the options (MenuItem) for the bottom navigation view and A1,B1,C1,D1,E1 are the entry fragments for those options repectivly
The the desired navigation is that the entry AND exit point of the app will always be option A (entry fragment A1). So if the user navigates to another option as long as is in the entry fragment for that option, the behaivor for any back navigation should be to go to option A.
The problem I have is that the bottomNavigationView is always present as a requirement so the user could navigate to any option at any time.
For exemple if the user navigates to option E then in E1 takes an action that navigates to E2 an then navigates to option B if the user press the back button the app should go to option A because is in the entry fragment B1.
Also if the user navigates to option A using the bottom navigation view an then press the back button, as we are in the exit point we should be finishing the app.
In the OnNavigationItemSelectedListener I replace the current fragment for the entry fragment for the selected option using beginTransaction.replace for any options other than option A I add the addToBackStack(null) but this alone does not matches the desired navigation since if the user press the back button, insted of navigate to option A it navigates to the previous selected option. A have also tried to pop the back satck before replacing the fragment using popBackStack(BACK_STACK_HOME_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) and replacea adding addToBackStack(BACK_STACK_HOME_TAG) but somehow when selected a second option instead of showing the entry fragment for the selected option it shows the Fragment A1
navBar.setOnNavigationItemReselectedListener {
when(it.itemId) {
R.id.optionA -> {
// Removes all entries in the backstack if any
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack(
null,FragmentManager.POP_BACK_STACK_INCLUSIVE
)
return#setOnNavigationItemSelectedListener true
}
// Replaces/add the entry fragment
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentHost, FragmentA1())
.commit()
return#setOnNavigationItemSelectedListener true
}
R.id.optionB -> {
// Removes all entries in the backstack up to BACK_STACK_HOME_TAG
supportFragmentManager.popBackStack(
BACK_STACK_HOME_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
// Replace the fragment with the entry FragmentB1
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentHost, FragmentB1())
.addToBackStack(BACK_STACK_HOME_TAG)
.commit()
return#setOnNavigationItemSelectedListener true
}
R.id.optionC -> {
// Removes all entries in the backstack up to BACK_STACK_HOME_TAG
supportFragmentManager.popBackStack(
BACK_STACK_HOME_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
// Replace the fragment with the entry FragmentC1
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentHost, FragmentC1())
.addToBackStack(BACK_STACK_HOME_TAG)
.commit()
return#setOnNavigationItemSelectedListener true
}
...
return#setOnNavigationItemSelectedListener false
}
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
return
}
super.onBackPressed()
}
You should just override the Activity.OnBackPressed() method in a BaseActivity which every of your activities will inherit.
In that function, just check if the current Activity is of type A. If yes, exit the app, otherwise, launch Activity A.
Set BACK_STACK_HOME_TAG state only when adding fragment A1. No need to pop back state when adding fragment B1, C1, D1...
Override onKeyDown() in fragment A1, make it quit app:
System.exit(0);
Override onKeyDown() in fragment B1, C1, D1..., set it back to A1 like:
fragmentManager().popBackStack(BACK_STACK_HOME_TAG, 0);
For A2, B2, C2, D2..., just pop itself to go back to A1, B1, C1, D1...
fragmentManager().popBackStack();
I am working with navigation component and bottom navigation
val navController = indNavController(R.id.nav_host_fragment)
bottom_navigation.inflateMenu(R.menu.bottom_navigation_menu)
bottom_navigation.setupWithNavController(navController)
and I am facing the next issue:
When an item is selected in the bottom navigation, then a fragment is loaded. The problem comes when I press again the same item, then a new fragment will be loaded, which it does not make sense at all.
Example:
1- User selects menu item A, then FragmentA is loaded.
2- User selects again menu item A, then a new FragmentA will be loaded,
I was trying to use
bottom_navigation.setOnNavigationItemSelectedListener { }
But then the bottom navigation will not work with the navController.
So the question is: there is a way to handle this situation in order to load again a new fragment when the user is in that screen already?
Finally, I was able to fix this issue.
As I said before, this code:
bottom_navigation.setupWithNavController(navController)
is using
bottom_navigation.setOnNavigationItemSelectedListener { }
so every time I select / reselect an item, the navController will load a new fragment. I checked the javadoc for setOnNavigationItemSelectedListener() and it says:
Set a listener that will be notified when a bottom navigation item is selected. This listener * will also be notified when the
currently selected item is reselected, unless an {#link *
OnNavigationItemReselectedListener} has also been set.
So what I did is to add the next line to my code:
bottom_navigation.setOnNavigationItemReselectedListener { }
and that's all. The navController will load a fragment when an item is selected but not when an item is reselected in the bottom navigation.
I prefer to use the listener from navController:
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}
So the listener is triggered when the destination changes - not by clicking bottom_navigation.
Because setOnNavigationItemSelectedListener is already used when setupWithNavController is declared.
Try this to ignore the user's click on the same selected item:
bottom_navigation.apply {
setOnNavigationItemSelectedListener {
if (it.itemId == bottom_navigation.selectedItemId) {
true
} else when (it.itemId) { ... }
when you use bottom_navigation.setOnNavigationItemSelectedListener { } before bottom_navigation.setupWithNavController(navController) the OnNavigationItemSelectedListener is overrided inside setupWithNavController function. So use
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}