Moving through multiple fragments android - android

Say i have fragment's A,B,C and D. The normal movement between fragments is A -> B -> C -> D. Now suppose i want to jump from A -> D, but onBackPressed() from D i want to be able to navigate back to C and then B respectively. Is there a way of doing this? The code i was attempting was something like this but it ucrrently is not working.
public void showNestedFragment(LinkedList<Fragment> fragments, boolean allowBack)
{
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (allowBack == false) // pop all thats in the backstack
getSupportFragmentManager()
.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
for(Fragment fragment : fragments){
fragmentTransaction.add(R.id.fragment, fragment);
if (allowBack)
{
fragmentTransaction.addToBackStack(null);
}
try
{
fragmentTransaction.commit();
}
catch (IllegalStateException e)
{
e.printStackTrace();
}
}
}

You have fragments so you can do anything you want. You should follow these steps to achieve this.
First declare all fragments in manifest in order A than B than C than D
So you can achieve normal navigation from A>B>C>D using simple approach of set visibility on of next fragment.
When you want direct navigation from A to D simply hide B and C fragment.
When you want to move from D>C>B>A simply visible B and C again and follow the simple navigation again.

Related

Prevent same fragments in backstack [duplicate]

This question already has answers here:
How to keep only one instance of a fragment, when switching with NavigationDrawer?
(4 answers)
Closed 2 years ago.
I have one Activity. In this activity, multiple fragments are there.
Fragments are in sequence to add,
A -> B -> C -> D -> B -> C -> D
Now, when i back action perform than sequence is,
D <- C <- B <- D <- C <- B <- A
But I have to perform back stack like this,
D <- C <- B <- A
what is the proper way to prevent the same Fragment in the backStack?
Here is my code for adding fragments,
if (fragment != null) {
val transaction = fragmentManager.beginTransaction()
if (bundle != null)
fragment.arguments = bundle
transaction.add(R.id.container_body, fragment)
transaction.addToBackStack(fragTag)
// Commit the transaction
transaction.commit()
}
And also for backstack perform,
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStackImmediate()
}
I don't know if this is perfect, but once I had done something like this. I had a stack of type integers (let's call it stackOfFragmentTracker). So suppose you have fragments f1, f2, f3, f4 and you did f1->f3->f4->f2. Then stackOfFragmentTracker would have content like this: 1->3->4->2.
Next, you create only one instance per fragment (so in my example, say f1, f2, f3, f4 each have only one instance) and you would keep them in backStack in INVISIBLE state:
final int ONE = 1; // a map to Fragment 1
final int TWO = 2; // a map to fragment 2
stackOfFragmentTracker.push(ONE);
ExampleFragment f1 = null;
if(fragmentManager.findFragmentByTag(ExampleFragment.TAG)==null){
f1 = new ExampleFragment();
fragmentManager.beginTransaction()
.add(R.id.flToMountOn, f1, ExampleFragment.TAG)
.addToBackStack(ExampleFragment.TAG)
.hide(f1) // <------- this makes your fragment invisible
.commit();
}
You would do that for all of your fragments.
Next, you need a Fragment variable (say, Fragment active), point it to your first fragment (i.e, where you want to go for the 1st time) and make it visible.
active = f1;
fragmentManager.beginTransaction().show(f1).commit();
To keep things clean, you can use these 2 methods:
private void hideActiveFragment(){
if(currentlyActiveFragment!=null){
fragmentManager.beginTransaction().hide(currentlyActiveFragment).commit();
}
}
private void showActiveFragment(Fragment nextFragment){
active = nextFragment;
if(active!=null){
fragmentManager.beginTransaction().show(active).commit();
}
}
Finally, whenever you go forward, you would push a number into stackOfFragmentTracker and simply make that fragment visible:
// say we are going to f2
void switchFragment(int n){
stackOfFragmentTracker.push(n); // you may keep a check if n is valid or not
if(n==1){
hideActiveFragment();
showActiveFragment(f1);
}else if(n==2){
hideActiveFragment();
showActiveFragment(f2);
} // ... ... ...
}
And onBackPress do this:
void insideBackPress(){
stackOfFragmentTracker.pop();
int top = stackOfFragmentTracker.top();
if(top==1){
hideActiveFragment();
showActiveFragment(f1);
}else if(top==2){
hideActiveFragment();
showActiveFragment(f2);
} // ... ... ...
}
I know the example is a bit messy, but I hope you get the point. Add necessary checks for various corner cases (if stackOfFragmentTracker isEmpty), or use a HashMap instead of the if-else block in my example. Good luck.
When creating the fragment set a tag for it, then later you can find it through the fragment manager and replace/create new one accordingly
FragmentManager fManager = getFragmentManager();
FragmentTransaction fTransaction = fManager.beginTransaction();
Fragment fragment = fManager.findFragmentByTag("uniqueTag");
// If fragment doesn't exist yet, create one
if (fragment == null) {
fTransaction.add(R.id.fragment_list, new ListFrag(), "uniqueTag");
}else { // re-use the old fragment
fTransaction.replace(R.id.fragment_list, fragment, "uniqueTag");
}
you can use dismiss() like if you go A to B, B To C and C to D when you press back and dismiss the fragment its removed from queue like its go like this D to C and C to B and B to A its make your stack clear and essay to handle

Conditional Navigation in Android Navigation Component

I have 3 fragments A, B and C with A being the start destination of my nav graph. When the user starts the app, I check in fragment A if there are previously stored results. If there are, I want to navigate straight to fragment C. I have managed to get this to work. However, when the user presses back in fragment C in this case, I want them to be taken to fragment B instead of A, and that's what I need help figuring out.
Note: Fragment A is just a setup fragment which is only visited once when the app starts. Which means when the user presses back from fragment B, they are taken to OS home screen.
You can override onBackPress() and replace whatever fragment you are in with FragmentB like this
#Override
public void onBackPressed() {
int stackCount = getFragmentManager().getBackStackEntryCount();
if (stackCount == 1) {
super.onBackPressed(); // if you don't have any fragments in your backstack yet.
} else {
// just replace container with fragment as you normally do;
FragmentManager fm = getFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);//clear backstackfirst and then you can exit the app onbackpressed from home fr
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.container, new FragmentB());
transaction.commit();
}
}
This is the original answer ,you can take a look answer by Rainmaker
You can override your onBackPressed on fragment C and call your navController to go back to fragment b creating a new action from C to B
navController.navigate(R.id.action_CFragment_to_BFragment)

How to remove Fragment transactions in middle

I have 5 fragments(say A, B, C, D, E) and one activity with a container.
Whenever i want to add a fragment to a container I'll be using the following code.
getSupportFragmentManager().beginTransaction().replace(R.id.mainContainerRL, fragment, tag).addToBackStack(tag).commit();
Let's say i added Fragment A.
Upon some action in A, I added fragment B.
Upon some action in B, I added fragment C.
Upon some action in C, I added fragment D.
Upon some action in D, I added fragment E.
Now my stack should be as follows.
A -> B -> C -> D -> E
Now upon some action in Fragment E, I need to remove fragments D, C, B so that when user click back, he should directly see Fragment A.
I tried using following code.
public void removeScreen(#NonNull String tag) {
FragmentManager manager = getSupportFragmentManager();
Fragment fragment = manager.findFragmentByTag(tag);
if (fragment != null) {
FragmentTransaction trans = manager.beginTransaction();
trans.remove(fragment);
trans.commitAllowingStateLoss();
}
}
Upon some action in Fragment E, I called the above function with Tags of Fragment D, C, B(Tags are same as the one that i used for fragment transaction).
Now when i click back button fragment D is becoming visible but i was expecting fragment A.
It would be very helpful if somebody points out where am i going wrong.
If you want to reach exactly the same behavior that you've described, you can do it by this way:
FragmentManager manager = getSupportFragmentManager();
manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
This will clear all backstack until the bottom of stack will be reached.
I prefer using DialogFragment for this reason and pop them using interface callbacks and dismiss() function inside them. This is the easiest and quick way to implement what you are trying to do.
I think your problem occur here :
replace :
getSupportFragmentManager().beginTransaction().replace(R.id.mainContainerRL, fragment, tag).addToBackStack(tag).commit();
to:
getSupportFragmentManager().beginTransaction().add(R.id.mainContainerRL, fragment, tag).addToBackStack(tag).commit();
when you add your fragment to container,tou just add it ,if use"replace" method,you romove it from activirty's fagment manager,it case your "removeScreen" method did not work

Going from 3rd to 1st fragment in the backstack: Android

I have a sequence of event via which i have added three fragments to the backstack, one by one. Each of these fragments covers the full screen of the activity.
I have stored the is returned from the commit of Frag1.
Now in Frag3, based on a specific click, I want to go back to Frag1 directly and discard/pop all Fragments in between.
So, when this button is clicked i send a message to the activity which does the following:
getSupportFragmentManager().popBackStack(mFrag1Id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
But i just got a blank screen, so i assume no fragment was loaded.
I even tried:
In commit - fragmentTransaction.addToBackStack("Fragment1");
and then
getSupportFragmentManager().popBackStack("Fragment1", FragmentManager.POP_BACK_STACK_INCLUSIVE);
But it doesn't work.
Could someone please help me with this?
Thanks.
OK so I found the issue.
FragmentManager.POP_BACK_STACK_INCLUSIVE pops all the fragments including the one whose id passed as argument.
SO for example:
getSupportFragmentManager().popBackStack(mFrag1Id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Here it will pop everything on the stack including fragment whose id id mFrag1Id.
from third fragment you should call popBackStack();
twice (one to remove third fragment and the second to remove second fragment )
android.support.v4.app.FragmentManager fm = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.remove(ThirdFragment.this);
transaction.commit();
fm.popBackStack();
fm.popBackStack();
When you opened Fragment A and you Navigated to Fragment B and then to Fragment C and then You want to close Fragment C and B and land on Fragment A
Now in some scenario, you want to close Fragment C and Fragment B and you want to land on Fragment A... then use this logic of FragmentManager to do such task.
First get the number of fragment entries in back stack (When we are adding any fragment to addToBackStack("Frag1")) at that time fragment back stack entry will increase.
so get using this
FragmentManager fmManager = activity.getSupportFragmentManager();
Log.e("Total Back stack Entry: ", fmManager.getBackStackEntryCount() + "");
Now assume, you want to close current fragment (Fragment C) and your last fragment (Fragment B) so simple logic is getBackStackEntryCount -2 and at that time your back stack entry count will be 3 (Fragment A, Fragment B and Fragment C)
Here -2 is for because we want to go 2 fragment step back (Fragment C
and Fragment B)
So simple two line of Code is:
if (fmManager.getBackStackEntryCount() > 0) {
fmManager.popBackStack(fmManager.getBackStackEntryAt(fmManager.getBackStackEntryCount()-2).getId(), FragmentMaanger.POP_BACK_STACK_INCLUSIVE);
}
You can also do it by adding two time "popBackStack()" and will also work, but it not idle way to do this
FragmentManager fmManager = activity.getSupportFragmentManager();
fmManager.popBackStack();
fmManager.popBackStack();
If you want user to back at the beginning fragment, code snippet below will help you.
public static void popBackStackInclusive(AppCompatActivity activity) {
FragmentManager fragmentManager = activity.getSupportFragmentManager();
for (int i = 1; i < fragmentManager.getBackStackEntryCount(); i++){
try {
int fragmentId = fragmentManager.getBackStackEntryAt(i).getId();
fragmentManager.popBackStack(fragmentId, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} catch (Exception e) {
Timber.d("Fragment Back Stack Error: %s", e.getLocalizedMessage());
}
}
}
Also if you want to prevent user to close app when no fragments at back stack, take a look at below.
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
if(fragmentManager.getBackStackEntryCount() > 1) {
super.onBackPressed();
} else {
// TODO: Show dialog if user wants to exit app or;
//finish();
}
}

How to correctly handle multiple fragment change in Android

Assume I have 4 fragments A B C and D.
A and B are major fragments, C and D are minor.
I use navigation drawer to switch fragments.
A is the default starting fragment.
I want to achieve following features but cannot figure out how to play with the fragment manager and transactions.
A -> B or B -> A, replace current fragment, do not push backstack, but I want to keep the current fragment status (e.g. list view position) after navigate back
A/B -> C/D, add C/D on top of A/B, using back button to navigate back to A/B.
C -> D or D -> C, replace current fagment
C/D -> A/B, remove current fragment C/D and show A/B
Is the only way to implement this function that I should write some complicated function for switching the fragments (and also need to keep what is current fragment and what is the wanted target fragment)?
Is there better way out?
According to #DeeV 's answer, I came out with something like following.
LocalBrowse and WebsiteExplore are main fragments while Settings and About are sub fragments.
It seems to work fine but still a little bit ugly, any better idea?
private void switchToFragment(Class<?> targetFragmentClz) {
if(mCurrentFagment!=null && mCurrentFagment.getClass().equals(targetFragmentClz)) {
return;
}
BaseFragment targetFragment = null;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if(targetFragmentClz.equals(LocalBrowseFragment.class)
|| targetFragmentClz.equals(WebsiteExploreFragment.class)) {
if(mCurrentFagment instanceof SettingsFragment //mCurrentFragment will not be null this time
|| mCurrentFagment instanceof AboutFragment) {
transaction.remove(mCurrentFagment);
}
if(mCurrentMainFagment==null || !mCurrentMainFagment.getClass().equals(targetFragmentClz)) {
targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
targetFragment.setHasOptionsMenu(true);
transaction.replace(R.id.ac_content_frame_main, targetFragment);
mCurrentMainFagment = targetFragment;
}
} else {
targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
targetFragment.setHasOptionsMenu(true);
getSupportFragmentManager().popBackStackImmediate();
transaction.replace(R.id.ac_content_frame_sub, targetFragment)
.addToBackStack(null);
}
transaction.commit();
mCurrentFagment = targetFragment;
}
One method that I can think of is to stack the two types of fragments on each other. So a system like this:
<FrameLayout>
<FrameLayout id="main_container">
<FrameLayout id="sub_container">
<FrameLayout>
Would mean that you have two containers holding fragments. The top one completely covers the other. Thus, you could have two method likes this:
public void swapMainContainer(FragmentManager fm, Fragment frag)
{
fm.beginTransaction().
.replace(R.id.main_container, frag, "TAG")
.commit();
}
public void swapSubContainer(FragmentManager fm, Fragment frag)
{
fm.popBackstackImmediate();
fm.beginTransaction()
.replace(R.id.sub_container, frag, "SUBTAG")
.addToBackStack(null)
.commit();
}
So if you use swapMainContainer() only with Fragment A and Fragment B, they will constantly replace each other but the commits won't be added to the backstack.
If you use swapSubContainer() only with Fragment C and Fragment D, they will likewise replace each other, but "Back" will close them. You are also popping the backstack every time you commit a sub Fragment thus removing the previous commit. Though, if there's nothing in the backstack, it won't do anything.
To remove C/D, simply call popBackStack() and it will remove them from the stack.
The flaw in this approach however is if you have more than these two Fragments that are added to the backstack. It may get corrupted.
EDIT:
Regarding saving view state, the fragment itself will have to handle that via this method.

Categories

Resources