Handle fragment back navigation and bottom navigation view selected item - android

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();

Related

Fragment not associated with a fragment manager on navigating in Android

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.

Back From fragment B to Fragment A not working Kotlin

I have two fragments in my Kotlin code.
When I'm pressing some of the buttons then the First fragment will inflate the second fragment.
The second fragment is displayed and all works fine but when I'm pressing the back button then the Phone is going to the Home page (The application is minimized), when I click on the Recently viewed apps that open all the Opened applications on the screen and choosing my Application (that is opened) then the application got back to Fragment Alike its suppose to be.
But I don't understand why the application is minimized when I'm clicking on the back button?
I just want it to go back to fragment A and do not minimize the application.
This is the code to inflate the second fragment:
val fragment2 = details_frag()
val fragmentManager: FragmentManager? = fragmentManager
val fragmentTransaction: FragmentTransaction =
fragmentManager!!.beginTransaction()
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
fragmentTransaction.apply {
replace(R.id.fragSec, fragment2)
commit()
}
} else {
fragmentTransaction.apply {
replace(R.id.flFragment, fragment2)
commit()
}
}
The Code in the Main Activity that inflate the first fragment is:
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragLand, firstFrag)
commit()
}
supportFragmentManager.beginTransaction()
.add(
R.id.fragSec,
details_frag::class.java,
null
) // .addToBackStack(null)
.commit()
} else {
supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, firstFrag)
commit()
}
}
I don't see something unusual here, it all works just great but it's just minimize my app when I'm going from the second fragment to the first fragment...
(The first is inserted inside the Main Activity like you can see and I just swap the first fragment with the second one when someone clicks on something in my code...)
Thank you!!!
You need to call addToBackstack in the FragmentTransaction (like you're doing in the second example). That makes the current transaction you're performing (replacing fragment A with B) a new state on the stack. Then when the user hits back, that state can be popped off and the transaction reversed, so you're back with fragment A in place.
If you don't add the transaction to the backstack, it becomes part of the most recent state, which is when you added A - that state becomes "added A and then replaced it with B", and when you pop that off, it goes back to "before you added A"

Pressing onBackButton is coming back to my fragment but I dont want to

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
}

Why Fragmenttransaction replace() acts like a add()?

I have here a sample GitHub project. It's just an activity inflating other fragments in it, by tapping bottom tabs:
Applaunch inflates Fragment 1
Pressing 1.Tab -> inflates Fragment 1
Pressing 2.Tab -> inflates Fragment 2
Pressing 3.Tab -> inflates Fragment 3
and all used by means of this.supportFragmentManager?.beginTransaction()?.replace(). For me it acts like a FragmentTransaction.add(), because:
When I start the app (Fragment 1 is loaded) and press
2nd Tab for Fragment 2 (Toast Frag 2)
and 3rd Tab for Fragment 3 (Toast Frag 3)
then backbutton (Toast Frag 2)
again backbutton (Toast Frag 1)
All the stack is working back. So nothing was "replaced", everything was added?
Whenever I press back, I would like to load "Fragment 1", which is the initial Fragment for the activity. How?
I guess it is happening because the fragments are being replaced but they are also being added to the BackStack as follows:
BaseActivity.kt
// Note the .addToBackStack(null)
this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
From Documentation for addToBackStack
Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.
You may want to check this question to check more info about the difference between add, replace and addToBackStack.
If you don't wanna to "remember and restore" the fragment transition, just remove the call to addToBackStack.
Edit
If you always want to return to first fragment, you can do:
Keep the addToBackStack(). So, the navigation history will be retained
this.supportFragmentManager?.beginTransaction()?.replace(resId,
newFragment)?.addToBackStack(null)?.commit()
Override onBackPressed on MainActivity or BaseActivity and request to always return to first transaction commit:
override fun onBackPressed() {
if(this.supportFragmentManager?.getBackStackEntryCount() > 0) {
this.supportFragmentManager?.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
super.onBackPressed()
}
}
Just note this is a test code and I could not verify. Also, I'm not familiar with Kotlin. So, this was the best code I could come with... Test and let me know the result... For any case, you can get the ideia.
Another option:
Remove the addToBackStack():
this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
Then, you must manually control which fragment should be displayed when user presses the back key... something like:
private var int : mCurrentFragment = 1
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
mCurrentFragment = 1
...
}
R.id.navigation_dashboard -> {
mCurrentFragment = 2
...
}
R.id.navigation_notifications -> {
mCurrentFragment = 3
...
}
}
false
}
override fun onBackPressed() {
if(mCurrentFragment != 1) {
replaceFragment(R.id.container_main_layout, Fragment1())
} else {
super.onBackPressed()
}
}

Unclude fragment from Backstack using Navigation component

I have Fragments X, A, B, and i'm using Navigation architecture component to navigate between them.
Fragments A, B specific, but Fragment X can be any(C,D,...);
Fragments A and B from Bottom Navigation and their "navigations icons" always on the screen, it means user can go to A or B anytime from any Fragment(include A and B):
X -> A -> B
X -> B -> A
A -> B -> X
A -> B -> A
//another ways
My problem about this case:
X -> A -> B -> A -> B -> ?
If user started from X, reached ? and begin to go back by "back" button, he goes throw A,B several times:
User pressed back:
? -> B -> A -> B -> A -> X
But I want "to exclude" fragments from backstack if they already on it:
? -> A -> B -> X
If user navigate:
X -> A -> B -> A
I want to see:
A -> B -> X
Not:
A -> B -> A -> X
I'm trying to do it with Pop To, but it can return me on one one concrete Fragment only. I need to return on my started X Fragment, not hardcoded. Inclusive and Single top is not about it.
I'm not sure i can do it with basic Navigation component, so i need your advice. If i can't do it, what way should i use? Is there any good practices about it?
UPD:
I'm using global points to navigate between Fragments. It's how my navigation looks like:
The right|bottom chain is X, i can navigate from any of it to to not chanied fragments using bottom navigation. It's Single Activity app, to navigate i'm using just:
//Using global points
findNavController(R.id.host).navigate(R.id.toLibrary)
the following Solution employs Fragment Visibility Technique in order to Manage Fragments Backstack when onBackPressed().
at first step
We Assign a Tag to each Fragment when it's Called & Invoked to make it possible to Recognize which of Fragments have been Added to Backstack and are predecessors.
by below code, we assign a tag to a Fragment that is going to be Transacted.
fragmentManager.beginTransaction()
.replace(R.id.FragmentHolder, Fragment_A OR Fragment_B OR Fragment_ANY, "A or B or ANY")
.addToBackStack(tag).commit();
Remember you must assign a tag to any Fragment that you want to be Transacted.
In the second
We are going to Handling public void onBackPressed().
You MUST ignore super.onBackPressed() Because, we don't Want to defualt onBackPressed Method to Impact Backstack(as it is) while back button is pressed. Moreover, we Want to Handle Backstack ourselves.
Here you go
#Override
public void onBackPressed() {
if(isVisible("A")) { //Go some where else you wanna go }
else if(isVisible("B")){ //Go some where else you wanna go }
else if(isVisible("X or any"){ //Go some where else you wanna go }
else { //Go default page }
}
public boolean isVisible(String tag){
try {
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null && fragment.isVisible())
return true;
else
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
When back button is pressed, We check which Fragment is already Visible and do Redirect the user to the Corresponding Predecessor Fragment.
For Ex: A - > B OR A -> X OR X -> B
I am using this Technique for a Released Application and all good.
In fragmentTransaction instead of using addToBackStack() or add(...) use fragmentTransaction.replace(int containerViewId, Fragment fragment, String tag)

Categories

Resources