How to remove Fragment transactions in middle - android

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

Related

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)

Android fragment simple backstack

I have one activity containing one container that will receive 2 fragments.
Once the activity initialises i start the first fragment with:
showFragment(new FragmentA());
private void showFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment, fragment.getTag())
.addToBackStack(fragment.getTag())
.commit();
}
Then when the user clicks on FragmentA, I receive this click on Activity level and I call:
showFragment(new FragmentB());
Now when I press back button it returns to fragment A (thats correct) and when i press again back button it show empty screen (but stays in the same activity). I would like it to just close the app (since the activity has no parent).
There are a lot of posts related with Fragments and backstack, but i can't find a simple solution for this. I would like to avoid the case where I have to check if im doing back press on Fragment A or Fragment B, since i might extend the number of Fragments and I will need to maintain that method.
You are getting blank screen because when you add first fragment you are calling addToBackStack() due to which you are adding a blank screen unknowingly!
now what you can do is call the following method in onBackPressed() and your problem will be solved
public void moveBack()
{
//FM=fragment manager
if (FM != null && FM.getBackStackEntryCount() > 1)
{
FM.popBackStack();
}else {
finish();
}
}
DO NOT CALL SUPER IN ONBACKPRESSED();
just call the above method!
addToBackStack(fragment.getTag())
use it for fragment B only, not for fragment A
I think your fragment A is not popped out correctly, try to use add fragment rather replace to have proper back navigation, however You can check count of back stack using:
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
and you can also directly call finish() in activity onBackPressed() when you want your activity to close on certain fragment count.

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.

Fragment replaced still catch backPressed

I'm using one Activity which contains one Fragment at a time. I use support.v4.
The "onBackPressed" of Activity and Fragment is override, so I can't use the original back button, I have to override it myself.
So I'm in fragment A, I go to fragment B. Fragment B displays a list of results, and when I pick a line, it goes to Fragment C.
When I'm on C and I press back button, I want to go back on B with the results displayed. And if I press back button again, I'll go to A.
It looks simple.
I manage the come back from C to B. The thing is when I hit the back button on B, it calls Fragment C backPressed.
A -> B -> C -> B (display B but BackButton don't work anymore).
Some code :
backPressed in Fragment C
public void backPressed() {
FragmentTransaction transaction = ((FragmentActivity)m_context).getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.mainactivity_fragmentcontainer,((FragmentActivity)m_context).getSupportFragmentManager().findFragmentByTag("customtag"), "customtag");
transaction.commitAllowingStateLoss();
}
Fragment B has the tag "customtag".
When I replace B by C:
FragmentC newFrag = new FragmentC(m_context);FragmentTransaction transac = ((FragmentActivity)m_context).getSupportFragmentManager().beginTransaction();
((FragmentContainerActivity)m_context).setCurrentFragment(newFrag);
transac.replace(R.id.mainactivity_fragmentcontainer, newFrag);
transac.addToBackStack(null);
transac.commitAllowingStateLoss();
Any idea why the fragmentC which is replaced (the display changes and I can see fragment B) still get the back button action?
Thanks for your help.
EDIT
Code of backPressed in Fragment B (however the application never goes there when it comes from Fragment C).
#Override
public void backPressed() {
Log.e("FragmentB", "backPressed");
//I have 2 views in FragB. If View2 is visible, I make it gone and display View1
if(mainView.findViewById(R.id.view2).getVisibility()==View.VISIBLE){
displayView1();
}else{
if(fromFragmentX){
FragmentX fragX = new FragmentX(ctx);
FragmentTransaction transac = ((FragmentActivity)ctx).getSupportFragmentManager().beginTransaction();
((FragmentContainerActivity)ctx).setCurrentFragment(fragX);
transac.replace(R.id.mainactivity_fragmentcontainer, fragX);
transac.commitAllowingStateLoss();
}else{
super.backPressed();
}
}
}
SECOND EDIT
I realize I forgot to precise something.
When I'm replacing fragment B by fragment C, I'm doing it in the ArrayAdapter of the ListView in Fragment B, but it shouldn't change anything.
Anyway, I tried something different somewhere else in the app.
In FragmentU, I replace FragmentU by Fragment C (always the same), and I'm setting an attribute in Fragment C which is :
private CustomFragment fragmentFrom;
So on backPressed in FragmentC, in case I'm from Fragment U, I call :
FragmentTransaction transaction = ((FragmentActivity)ctx).getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.mainactivity_fragmentcontainer, fragmentFrom);
transaction.commitAllowingStateLoss();
And I still got the same problem. When coming back from Fragment C, the Fragment U is displayed, and works well, but the back button call FragmentC.backPressed.

how to know which fragment called another fragment?

I have this issue in Android. Consider three Fragments: A, B, C. C can be called from A or from B. Is there anyway to know from which fragment, C was called?
Edited
Ok guys I'm going to explain what I'm trying to do. Suppose I have this call: A calls B, B calls C.
When I press the back button in C It gets me to B, thats fine. But when I press the back button again, it takes me to C, instead of A.
This is my code:
#Override
public void onBackPressed() {
//this is the current fragment
Fragment fragmentActual = this.getSupportFragmentManager().findFragmentById(android.R.id.tabcontent);
String fragmentTag = fragmentoActual.getTag().toString();
//If I press the back button in C:
if(fragmentTag.equals("TAG C")){
Fragment removefragment = this.getSupportFragmentManager().findFragmentByTag("TAG B");
Fragment fragmentClient = this.getSupportFragmentManager().findFragmentByTag("TAG C");
//If Im NOT passing arguments to B, I know this is a new form
if(removefragment.getArguments()== null){
//I always pass arguments to fragment C, but just in case:
if(fragmentClient.getArguments()!= null){
Bundle mArguments = fragmentClient.getArguments();
//FRAGMENT B
FragmentB fragmentB = new FragmentB ();
fragment.setArguments(mArguments);
FragmentManager manager = this.getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.addToBackStack(null);
ft.replace(android.R.id.tabcontent,fragmentB,"TAG B");
ft.commit();
}
}else{
super.onBackPressed();
}
}else{
super.onBackPressed();
}
}
Now I'm going to explain the code. Basically what it does is to replace fragment B, when fragment C is called. I do this, because I need to pass arguments to fragment B. But when I do the replacement, the "history" of the fragment B is lost, I mean when I press back button in B, I cant go back to fragment A (HERE IS WHY I WANTED TO KNOW IF I CAN KNOW WHO CALLS WHOM).
The firts time when I call fragment B, I dont pass arguments because is a blank form. But when I call to C, staying in B, I need to pass arguments to fragment B (when back button is pressed), so It can shows updated information.
Please if there something that is not clear, let me know so I can explain myself better.
Edited 2: This issue has something with this https://stackoverflow.com/questions/16703604/back-press-button-when-i-save-a-form-for-the-first-time-a-list-view-is-not-updat. Maybe it does my idea more clear.
The answer of Luksprog I think is the best: "You may want to tackle those issues. You always have the option of passing a Bundle with data to the newly created fragment mentioning who called it. Then using getArgument() in the fragment will know who called it.".
I haven't found another better way.
You can use the setTargetFragment method to set which was the parent fragment. Then you can use the method getTargetFragment to find out who called you.
What you are doing is transitioning between Fragments, call addToBackStack() as part of your FragmentTransaction:
i guess, This is what you need.
private final static String TAG_FRAGMENT = "TAG_FRAGMENT";
private void showFragment() {
final Myfragment fragment = new MyFragment();
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment, fragment, TAG_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
#Override
public void onBackPressed() {
final Myfragment fragment = (Myfragment) getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if (fragment.allowBackPressed()) { // and then you define a method allowBackPressed with the logic to allow back pressed or not
super.onBackPressed();
}
}
You can use FragmentManager for creating a back stack of your fragments. You also have to work with Fragment Transactions first. Further informations see: http://developer.android.com/guide/components/fragments.html#Transactions

Categories

Resources