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
Related
I need to display a Fragment with 2 fragments before it being added into back stack. However addToBackStack method belongs to FragmentTransaction so I cannot add all three of them in the single FragmentTransaction cause all three of them will be removed by back button click. But if I use three different FragmentTransations then until the third fragment becomes visible two previous ones become visible to the user too.
Is there a way I can add three Fragments into back stack without making first two of them visible during transaction?
I am not sure you can do that using the native API. Nonetheless you can implement your own stack using the Queue Interface
add an OnBackStackChangedListener to fragmentmanager , then when the last backstack entry is fragment 2 popBackStack two times like this :
0 FragmentHome ( back_stack_name : "fragment_home" )
1 Fragment1( back_stack_name : "fragment_1" )
2 Fragment2 ( back_stack_name : "fragment_2" )
3 Fragment3 ( back_stack_name : "fragment_3" )
code :
note that you should add OnBackStackChangedListener in fragment3 and then after poping backstack remove it from fragmentmanager
// add in fragment3
final FragmentManager fragment_manager = getActivity().getSupportFragmentManager();
fragment_manager
.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (fragment_manager.getBackStackEntryCount() > 0) {
String last_fragment_name = getLastBackStackFragmentName(fragment_manager);
if (last_fragment_name.equals("fragment_2")) {
fragment_manager.removeOnBackStackChangedListener(this);
fragment_manager.popBackStack();
fragment_manager.popBackStack();
}
}
}
});
private String getLastBackStackFragmentName(FragmentManager fragment_manager ) {
int back_stack_count =fragment_manager.getBackStackEntryCount();
String last_fragment_name = "";
if (back_stack_count>0) {
last_fragment_name = fragment_manager.getBackStackEntryAt(
back_stack_count).getName();
}
return last_fragment_name;
}
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();
}
}
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.
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.
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