Fragment does not pop out from backstack? - android

I have three fragment A, B and C
This is the path : A -> B -> C : A go to B and B go to C
When I go back from the fragment C , I want to go to the fragment A.
A <- C : C go back to the root fragment A
A <- B : B go back to the root fragment A
But My problem is when I pressed back in the fragment C , I get this behviour : It seems that the fragment C is not cleared from the backstack :
As for my code , this is the method of the replaceFragment :
public void replaceFragment(Fragment fragment, boolean withBackStack) {
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
String fragmentTag = fragment.getClass().getName();
fragmentTransaction.replace(R.id.frame, fragment, fragmentTag);
if (withBackStack)
fragmentTransaction.addToBackStack(fragmentTag);
try {
fragmentTransaction.commitAllowingStateLoss();
} catch (IllegalStateException ex) {
ex.printStackTrace();
}
}
First I called replaceFragment(new AFragment(), false); in the MainActivity , Then in the Fragment A when I clicked in the button, I called mListener.replaceFragment(new BFragment(), true);
Finally , in the Fragment B when I clicked the button , I called mListener.replaceFragment(new CFragment(), false);
Does anyone have an explanation for this behaviour ? The last fragment C shouldn't be cleared when I click backpressed ?
Here , you will find the whole example.
Thanks in advance!

Since you are not adding fragC to the backstack when backpressed wont pop the stored transaction it is not removed.instead here you have to remove the fragment by overriding backpress.Overiride backpress and check for the fragC and remove it and then call the pospstack to pop back the stored transaction.
Also store the instance of fragment as global to check if the fragment if fragC.
private Fragment mFragment;
Inside you method store the instance
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(mFragment.getClass().getName());
if (fragment != null && fragment.getClass().getName().equalsIgnoreCase(CFragment.class.getName())) {
getSupportFragmentManager().beginTransaction().remove(mFragment).commit();
}
getSupportFragmentManager().popBackStackImmediate();
} else {
super.onBackPressed();
}
}

Based on the answer of #Anonymous, I found a general solution for this problem :
#Override
public void onBackPressed() {
Fragment currentFragment= getSupportFragmentManager().findFragmentById(R.id.frame);
super.onBackPressed();
if(currentFragment!=null && currentFragment.isVisible()) {
getSupportFragmentManager().beginTransaction().remove(currentFragment).commit();
getSupportFragmentManager().popBackStack(currentFragment.getTag(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}

Related

How to redirect from last fragment to specific fragment on last fragment backpress

I have a problem about removing a specific Fragment from Backstack.
Like, I have Five Fragments A -> B -> C -> D -> E and all Fragments call from same Activity with add to Backstack and add not use replace.
Calling order; A -> B -> C -> D -> E.
When Fragment-E (last fragment) is my current Fragment and pressed the Back Button then I want to go Fragment-B. That means I don’t require Fragment-C, Fragment-D and Fragment-E.
Also, When Fragment B is there and presses back button then Fragment A will be called. This will also happen with all the fragment simultaneously.
One more thing All Fragment call API to fetch data from the server and fill to list.
Help to achieve this scenario.
You have a method called instanceof for Fragments. So when you are in Fragment E try to check like
while adding do fragmentTransaction.addToBackStack("fragmentC");
if(currentFragment instanceOf FragmentE) {
fragmentManager.popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
You need to override onBackPressed() in your Activity, From where your added Fragment.
You can create your own logic. like check if current fragment is instanceof FragmentE You need to call getFragmentManager().popBackStack(); three time to remove Fragments C -> D -> E
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
if(getFragmentManager().findFragmentById(R.id.ad_container) instanceof FragmentE) {
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
// pop fragment D,C from backstack
// navigate to fragment B
}
} else {
super.onBackPressed();
}
}
Hope this will help
In you activity Override onBackPressed() method and add below code
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
if (fragment instanceof FragmentE) {
getSupportFragmentManager().popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
} else {
super.onBackPressed();
}
}
Remember don't forget to add .addToBackStack("whichFragment") before transaction.commit(); while adding each fragments.
getSupportFragmentManager().popBackStack ("fragmentC", FragmentManager.POP_BACK_STACK_INCLUSIVE) //this essentially means also remove FragmentC

Is there anyway to swap or reorder fragment BackStack entry?

am creating multiple fragment app, which require easy changing of fragment tabs, whose BackStack entry must be kept. On user click fragment must be pop up immediate. I think reordering BackStack is the best option. Do anyone know how to reorder BackStack ?
I have 5 fragments A,B,C,D,E. if user open fragment like A ---> B ---> C ---> D ---> E . using default back stack,back key it works fine. But when user opens A ---> B ---> C ---> D ---> E ---> B. After this if user click back, default back stack will goes to A.
There is no api for this usecase, but there are other means to do so:
Example
FragmentBackStackModifyActivity
use non-swipable viewpager and on addOnPageChangeListener create arraylist and save all pages that opened. onBackPressed will compare arraylist and show pages.
The user follows the path A ---> B ---> C ---> D ---> E then he wants to come to B. So what you should do is:
check if B is already in the back stack or not.
If it is there, then popBackStackImmediate to return to B
If it is not there, then add the fragment B normally.
So replace fragment should be something like this:
public void replaceFragment(Fragment fragment) {
String fragmentTag = fragment.getClass().getCanonicalName(); //unique name for the fragment, which will be used as tag.
FragmentManager fragmentManager = getSupportFragmentManager();
boolean isPopped = fragmentManager.popBackStackImmediate(fragmentTag, 0); // trying to return to the previous state of B if it exists.
if (!isPopped && fragmentManager.findFragmentByTag(fragmentTag) == null){ // fragment is not in the backstack, so create it.
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.frame_layout, fragment, fragmentTag);
ft.addToBackStack(fragmentTag);
ft.commit();
}
}
Remember, this will return to the previous state of B.
TIP: Since you don't want two instances of any fragment, So try to create a singleton instance of them. Because, if you are creating a new instance of the fragment and that fragment already exists in the back stack then the new instance will be ignored (because you are returning to the previous state).
You Can Try this...
onBackpressed
#Override
public void onBackPressed() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.frame_container);
if (f instanceof FragmentE)
{
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.popBackStack(fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount()-1).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else if (f instanceof FragmentD) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.popBackStack(fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount()-2).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
else if (f instanceof FragmentC) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.popBackStack(fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount()-3).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
else if (f instanceof FragmentB) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.popBackStack(fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount()-4).getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
else
{
super.onBackPressed();
}
}
for back remove stack
final android.app.FragmentManager fm = getFragmentManager();
fm.addOnBackStackChangedListener(new android.app.FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (getSupportFragmentManager().getBackStackEntryCount() ==0) {
// dLayout.closeDrawers();
finish();
}
else
{
// dLayout.closeDrawers();
}
}
});
May be call addToBackstack() only when the fragment is not added before
Suppose as in your case when user is in Fragment E and when user calls Fragment B check if the Fragment B is already added in back stack
if yes, Then dont call addToBackstack for that FragmentTransaction.
else ,call addToBackstack()
check my sample code:
private void addFragment (Fragment fragment){
boolean isFragment=false;
String backStateName = fragment.getClass().getName();
FragmentManager fm = getFragmentManager();
for(int entry = 0; entry < fm.getBackStackEntryCount(); entry++){
Log.i(TAG, "Found fragment: " + fm.getBackStackEntryAt(entry).getId());
if(fm.getBackStackEntryAt(entry).getId().equalsIgnoreCase(FragmentB))// check if fragment is in back stack
{
isFragment=true;
}
}
if (!isFragment){ //fragment added to back stack, create it.
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, FragmentB).addToBackStack(FragmentB).commit();
}else {//else dont call addToBackStack()
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, FragmentB).commit();
}
}
Just place this code in your MainActivity
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
//super.onBackPressed();
if (getFragmentManager().getBackStackEntryCount() == 1) {
finish();
}
getFragmentManager().popBackStack();
}
}
Here getFragmentManager().getBackStackEntryCount() == 0 will checks that whether the stack entry is at 0 or not, if it's on 0 then the Activity will be finished.
In your case A --> B --> C --> D --> E --> B
A will be on 0,
B will be on 1,
C on 2,
D on 3,
E on 4,
B on 5
So when you click on back, the if condition will checks that the backstack count is on 0.

Fragment backstack and toggle

I'm having an special use case where I need to switch between two fragments. The issue I'm having is that for the second fragment I need to persist it's state, and the only thing that seems to be working for that is to add it to the BackStack.
I rely on the support fragment manager to replace the fragments:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out)
.replace(R.id.fragment_container, fragmentA)
.commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
boolean isRestored = false;
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
isRestored = true;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in,
R.anim.frag_fade_out,
R.anim.frag_fade_in,
R.anim.frag_fade_out);
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if(!isRestored){
transaction.addToBackStack(TAG_FRAG_B)
}
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
This in combination with the onBackPressed() override:
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else {
// Back key was pressed and we are on fragment A - at this state we simply want to go back to the
// previous section
super.onBackPressed();
}
}
Using this implementation I make sure I reuse fragment B and keep it's state so that it doesn't look like it is created from scratch each time. I also make sure that when I go back, I can go only from fragment B to A and not from fragment A to B.
The issue I encountered is that when super.onBackPressed(); is called and more than one fragment was added(replaced actually, as I want only one active fragment at a time) through the fragment manager, it will throw an exception:
java.lang.IllegalStateException: Fragment already added: FragmentA{af9c26b #0 id=0x7f0e00d3}
This is happening only when the active fragment is FragmentA. I have a suspicion that this is because of the BackStack implementation, but as I've said, I only want the second one to be persisted.
How can I fix this? I am missing something?
I have managed to implement an work-around for this, although it is a little hacky.
Because I need to keep the state of FragmentB, I am forced to add it to the BackStack, but this will actually affect what transition is reversed when onBackPressed() is called.
To avoid this, I had to update the logic for the back press and manually handle that case
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else if (isCurrentFragmentA()) {
getSupportFragmentManager().popBackStackImmediate(TAG_FRAG_A, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// Special case - because we added the fragment B to the BackStack in order to easily resume it's state,
// this will fail as it will actually try to add fragment A again to the fragment manager (it
// will try to reverse the last transaction)
super.finish();
} else {
// Usual flow - let the OS decide what to do
super.onBackPressed();
}
}
Also, I've optimized the toggle method a little bit:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
#SuppressLint("CommitTransaction") FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_A);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentA = (FragmentA) fragment;
}
// Replace current fragment with fragment A and commit the transaction
transaction.replace(R.id.fragment_container, fragmentA, TAG_FRAG_A).commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
}
// Replace current fragment with fragment B
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if (null == fragment) {
// No entry of the fragment B in the BackStack, we want to add it for future uses
transaction.addToBackStack(TAG_FRAG_B);
}
// Commit the transaction
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
I hope this can help others which need an similar flow.
PS: The fragment I want to persist is SupportMapFragment, so that my map isn't always redrawn, re-centered and populated with data every time I want to show it.

Synthesize Android fragment backstack

I have an activity that uses fragments to change views instead of launching new activities. Lets say I have 3 fragments A, B and C. When the application launches the default fragment is set to A. The user can click a button on A to transition to B -- same with B to C.
Therefore the backstack looks like:
[A] -> [B] -> [C]
What I need to do is deep link directly to fragment C from a notification while still building out the backstack so that when the activity is launched. Fragment C should be displayed while allowing the user to click the back button to get back to views B and A respectively.
You can make 3 separate transactions. This is a lot more natural than manually checking the state of the backstack.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showFragmentA();
if (getIntent().hasExtra("some_deep_link_flag")) {
showFragmentB();
showFragmentC();
}
}
private void showFragmentA() {
Fragment a = new Fragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, a)
.addToBackStack(null)
.commit();
}
private void showFragmentB() {
Fragment b = new Fragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, b)
.addToBackStack(null)
.commit();
}
private void showFragmentC() {
Fragment c = new Fragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, c)
.addToBackStack(null)
.commit();
}
If the stack is always going to A -> B -> C then you could override onBackPressed() in the activity and pop the back stack and check to see what the situation in the fragment stack is.
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getFragmentByTag("C") != null) {
if (getSupportFragmentManager().getBackStackEntryCount() == 2) {
getSupportFragmentManager().beginTransaction.replace(...).commit();
} else {
getSupportFragmentManager().popBackStack();
}
} else if (getSupportFragmentManager().getFragmentByTag("B") != null) {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
getSupportFragmentManager().beginTransaction.replace(...).commit();
} else {
getSupportFragmentManager().popBackStack();
}
} else {
//You are already on Fragment A
super.onBackPressed();
}
}
Like jmcdonnell40 suggests:
My guess is you should put an extra on the notification intent which makes clear that you are entering your Activity from the notification.
Then, in the activity's oncreate, check for that extra, and if it is present, just do the 2 necessary fragment transactions (fragment A -> B -> C) and add them to the backstack.
you're going to have to open your last fragment manually anyway.

Fragments are getting overlapped on back button

I have created 3 Fragments namely (FragmentA, FragmentB, FragmentC) and one MainActivity.
There is a button in each fragment which replaces itself with next Fragment till FragmentC.
I am replacing FragmentA (with) FragmentB (then with) FragmentC.
Transaction from FragmentA to FragmentB uses below function
#Override
public void fragmentreplacewithbackstack(Fragment fragment, String tag) {
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.contner,fragment , tag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
// fragmentManager.executePendingTransactions();
}
Transaction from FragmentB to FragmentC uses below function
public void fragmentreplace(Fragment fragment,String tag){
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.contner,fragment , tag);
fragmentTransaction.commit();
}
problem is when i press back button from FragmentC, FragmentC and FragmentA overlap with each other.
You need to add Fragment C to backstack as well if you wanna go to Fragment B on back press from here.
So call the below for Fragment C as well.
fragmentTransaction.addToBackStack(null);
EDIT - Change this current method you are using to go from B to C,
public void fragmentreplace(Fragment fragment,String tag){
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.contner,fragment , tag);
fragmentTransaction.addToBackStack(null); //this will add it to back stack
fragmentTransaction.commit();
}
I had the same problem and this answer of Budius helped me a lot.
You can solve your issue in the following way:
1) Instantiate the following listener (you have to keep reference of the FragmentC's instance) and, since fragmentA is the only fragment added to the backstack, when the user presses the back button, the number of transactions in the backstack will be zero
private OnBackStackChangedListener backStackChangedListener = new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount()==0) {
if(fragmentC!=null) {
getSupportFragmentManager().beginTransaction().remove(fragmentC).commit();
}
}
}
};
2) Add the listener in the MainActivity when the latter is started
getSupportFragmentManager().addOnBackStackChangedListener(backStackChangedListener);
3) Remove the listener when the activity is stopped
why don't you just make a fragment activity with a frame layout in which you can replace that frame with any fragment..
Screen extends FragmentActivity
{
protected void onCreate(Bundle b)
{
super.onCreate(b);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.work);
callPerticularScreen(screenNumber,false,null,null);
}
public void callPerticularScreen(int screenNumber,boolean addToBackStack,String nameTag,Bundle b)
{
switch(screenNumber)
{
case registrationScreen:
callFragForMiddleScreen(registrationPageFrag,addToBackStack,nameTag);
break;
case dashboardScreen:
callFragForMiddleScreen(dashboardFrag,addToBackStack,nameTag);
break;
default:
break;
}
}
}
now from any fragment screen you can call this function to replace your fragment with another..like..
private void callFragForMiddleScreen(Fragment frag,boolean addToBackStack,String nameTag)
{
transFragMiddleScreen=getFragManager().beginTransaction();
transFragMiddleScreen.replace(getMiddleFragId(),frag);
if(addToBackStack)
{
transFragMiddleScreen.addToBackStack(nameTag);
}
transFragMiddleScreen.commit();
//getFragManager().executePendingTransactions();
}
from your fragement just call
Frag1 extends Fragment
{
onActivityCreate()
{
middleScreen=(Screen) getActivity();
middleScreen.callPerticularScreen(1,true,"tag");
}
}
layout for fragment activity..
<RelativeLayout
android:id="#+id/rl2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/rl" >
<FrameLayout
android:id="#+id/middle_screen"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</RelativeLayout>
just don't add the fragment B to back stack so that you can come from c-> a directly.
25 support library
for example if you have
A -> B -> C
A id is 0
B id is 1
C id is 2
to rollback to A fragment just call
getFragmentManager().popBackStackImmediate(0 /* ID */, 0 /* flag */);
0 - is ID of backstack entry to rollback
or
popBackStackImmediate(1, FragmentManager.POP_BACK_STACK_INCLUSIVE)
FragmentManager.POP_BACK_STACK_INCLUSIVE, it means you want also remove supplied ID
also you can call
getFragmentManager().popBackStackImmediate("stack_name", 0)
"stack_name" it's a name of backstack to revert
for example
A -> B -> C -> D
A in MAIN_BACK_STACK
B in MAIN_BACK_STACK
C in SEPARATE_BACK_STACK
D in SEPARATE_BACK_STACK
if you want to revert to fragment B
just call
getFragmentManager().popBackStackImmediate("MAIN_BACK_STACK", 0)
or
getFragmentManager().popBackStackImmediate("SEPARATE_BACK_STACK", FragmentManager.POP_BACK_STACK_INCLUSIVE)

Categories

Resources