I am working with fragments and the navigation flow like
Fragment A -> Fragment B -> Fragment C -> Fragment D
Form fragment D I need to navigate to fragment A by clearing back stack but the problem is in onCreateView() method of fragment C I am showing one dialog
When I am navigating from D to A by clearing the back stack Over fragment A same pop-up appears which was shown in on fragment C
below is the code I am using to clear the stack
FragmentManager fm = getActivity().getSupportFragmentManager();
for (int i = 0; i < fm.getBackStackEntryCount(); ++i) {
fm.popBackStack();
}
The problem you have lies in the way you are dealing with the fragments lifecycle. You want Fragment C to do onCreateView only once (to show the popup), but onCreateView get's called every time the View is created (e.g, every time you call remove on a fragment(replace works pretty much the same, remove + add) and then add it back from backstack with popbackstack).
For your problems there are two solutions:
Cleaner one: instead of showing your popup from onCreateView, call it from onCreate in Fragment C. With this you will guarantee that it only get's called when the fragment instance is created.
Not so clean: Instead of using replace between Fragment C and D transaction, call add, this way when you pop the backstack in Fragment D, Fragment C onCreateView won't be called because the View was never destroyed (never called remove/replace upon).
Related
I am using navigation controller component. let say I have 4 fragments
A --> B --> C or D
from fragment A, it can only go to fragment B. but if we are in fragment B, it can go to fragment C or to fragment D.
I want to perform method workXYZ() in onResume fragment B if it comes from fragment A, and I want to perform method doSomethingABC() in onResume fragment B if it comes from fragment C or fragment D.
from fragment A to fragment B I use the code below:
val BDestination = FragmentADirections.actionToFragmentB()
Navigation.findNavController(fragmentView).navigate(BDestination)
to reach fragment C and fragment D from fragment B, I use global action. because fragment C & D are actually not only used by fragment B. so I use the code like below to go to fragment C or D
val CDestination = BFragmentDirections.actionGlobalFragment()
Navigation.findNavController(fragmentView).navigate(CDestination)
and from fragment C or fragment D, I use back button to be back to fragment B.
I have tried to use safe arguments boolean comesFromFragmentA = true in navigation graph in order to give a sign that fragment B comes after fragment A. but unfortunately, that value on safe arguments boolean comesFromFragmentA will remain the same (true) if it comes from fragment C or D.
so what should I do if want to know if the fragment B appears after previous fragment or from the fragment afterward ?
Fragment arguments are mutable so you can change them at any time. Therefore you can update that argument value right before you navigate to signify that future onResume() calls will be after you return to this Fragment:
// Set the comesFromFragmentA argument to signify that the next onResume()
// will be when you come back to this Fragment
arguments.putBoolean("comesFromFragmentA", false)
// Now navigate
val CDestination = BFragmentDirections.actionGlobalFragment()
Navigation.findNavController(fragmentView).navigate(CDestination)
I have a instance of FragmentManager (variable frgManager), I replace to new fragment by:
// Fragments all use the same container
frgManager.beginTransaction().replace(containerId, nextFragment, MyTag).commit();
Let's say I have three fragments A, B, C,
the app show fragments in the sequence of A-->B-->C. Everytime show next fragment, I call the code above.
The problem is after C is shown, I close C, then B is shown in foreground, but the onResume() of A is also called. Why? How to avoid that because I expect only B's onResume() get called in this case.
frgManager.beginTransaction().replace(containerId, nextFragment, MyTag).addToBackStack().commit();
Use addToBackStack when creating all of your fragments. This way, only 1 can be active at once.
I've a fragment A. I add() it with tag like this:
fragmentTransaction.addToBackStack(special_tag);
Then I simply add() fragment B on top of fragment A. After that, I decide to remove fragment B and go back to fragment A using:
activity.fragmentManager.popBackStackImmediate(special_tag, 0)
When I reach the fragment A, it seems that fragment doesn't re-run it's lifecycle methods: onAttach(), onResume(), onCreate() ect.
Can someone explain this behavior and maybe suggest an alternative?
(I need to "refresh" the data when I come back to fragment A second time)
What is causing this result?
Is there a clean solution/work-around?
Update
Fragment B is GuidedStepFragment and does not have a .replace() function. I found that it has finishGuidedStepFragments(), but it behaves the same (it does not call fragment life cycle functions)
Situation (again):
Fragment A (Simple fragment) -> .add(Fragment B) (GuidedStepFragment) -> popBackStackImmediate() or finishGuidedStepFragments()
I add Fragment B like this:
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance())
Using fragmentTransaction.add(Fragment) doesn't remove Fragment A. What is actually happening is that Fragment A is still running behind Fragment B. Since Fragment A never stopped running, it's lifecycle has no need to retrigger.
Consider using fragmentTransaction.replace(Fragment) and replace the fragment in the container (fragment A) with fragment B. If you pop that transaction from the back stack, then Fragment A will reattach and follow your expected lifecycle.
Update
Since you seem to be using GuidedStepFragments from the leanback library, this is a little tricky. GuidedStepFragment actually performs replace(...) under the hood, but you're adding fragment B to a different container so the original behavior I mentioned doesn't apply.
I'm not super familiar with leanback (since it's usually only used for android tv), but I do know that you can at least do the following. If you keep track of your backstack size, when all of the GuidedStepFragments have been popped, you will have returned to your original fragment. For example, let's assume your backstack starts at zero:
activity.fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (activity.fragmentManager.getBackStackEntryCount() == 0){
// handle your updates
}
}
});
// the next line of code will add an entry to the backstack
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance());
// eventually when back is pressed and the guided fragment is removed, the backstack listener should trigger
I have some king of header in my activity, which says what kind of fragment is opened now. It's ok, when I'm just replacing one fragment by another, but I have a problem with handling backstack changes in onBackPressed. That's a part of my code in onBackPressed method:
Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment);
fragmentManager.popBackStack();
fragment = fragmentManager.findFragmentById(R.id.main_fragment);
in first row, fragment=FormFragment{41f01d58 #3 id=0x7f05005f}, and after calling popBackStack I have fragment=FormFragment{41f01d58 #3 id=0x7f05005f} again (but it should be another fragment, even not FormFragment instance).
Is there any way how to find out what fragment is popped from backstack after calling popBackStack?
First of all, usually you don't have to pop the fragment back stack yourself. If your activity is a FragmentActivity, its default onBackPressed() will do the work for you.
To update your header when the fragment is popped from the back stack, put the header update code in the fragment's onResume().
Given the application flow show in the graphic and textually described in the following.
Fragment 1 is the lowest fragment but not in the backstack by setting disallowAddToBackStack.
Fragment 2 is pushed onto the stack, using fragmentTransaction.addToBackStack().
A new instance of fragment 1 is pushed onto the stack.
The top most fragment (fragment 1) is popped from the stack.
Activity 2 becomes foreground.
Activity 1 becomes foreground.
Here is the generalized method I use to handle fragments:
private void changeContainerViewTo(int containerViewId, Fragment fragment,
Activity activity, String backStackTag) {
if (fragmentIsAlreadyPresent(containerViewId, fragment, activity)) { return; }
final FragmentTransaction fragmentTransaction =
activity.getFragmentManager().beginTransaction();
fragmentTransaction.replace(containerViewId, fragment);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (backStackTag == null) {
fragmentTransaction.disallowAddToBackStack();
} else {
fragmentTransaction.addToBackStack(backStackTag);
}
fragmentTransaction.commit();
}
Problem
When activity 1 resumes in the last step the lowest instance of fragment 1 also resumes. At this point in time fragment 1 returns null on getActivity().
Question
Why is a fragment which is not the top most on the stack resumed?
If resuming the fragment is correct - how should I handle a detached fragment?
When an Activity is not showing UI and then come to show UI, the FragmentManager associated is dying with all of your fragments and you need to restore its state.
As the documentation says:
There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
In your Activity onSaveInstanceState and onRestoreInstanceState, try saving you Fragment references and then restore them with something like this:
public void onSaveInstanceState(Bundle outState){
getFragmentManager().putFragment(outState,"myfragment", myfragment);
}
public void onRetoreInstanceState(Bundle inState){
myFragment = getFragmentManager().getFragment(inState, "myfragment");
}
Try this out and have luck! :-)
I don't see how this would happen, unless (based on how you described the steps) you've misunderstood how fragmentTransaction.addToBackStack() works: it manages which transactions are placed in backstack, not fragments.
From the android docs:
By calling addToBackStack(), the replace transaction is saved to the
back stack so the user can reverse the transaction and bring back the
previous fragment by pressing the Back button.
So if your step 2 looked something like this in code:
fragmentTransaction.replace(containerViewId, fragment2);
fragmentTransaction.addToBackStack();
fragmentTransaction.commit();
and your step 3:
fragmentTransaction.disallowAddToBackStack()//or just no call to addToBackStack - you do not say
fragmentTransaction.replace(containerViewId, newfragment1);
fragmentTransaction.commit();
At this point, Fragment2 will be removed from the backstack, and your backstack consists of the two Fragment1 instances. in Step 4 you pop the top one, which means you should have the bottommost Fragment1 now at the top.
This explains why it is the resumed fragment if you return to the activity. But not, i'm afraid, why it is apparently detached from its activity.
Android OS can and will create and destroy fragments when it sees fit. This is likely happening when you launch Activity 2 and return to Activity 1. I'd verify for sure that it isn't the actively displayed fragment. What is probably happening is that you are seeing it do some of the creation steps for fragment 1 before it does the creation steps for fragment 2.
As for handling the detached fragments you should take a look at this page. The gist of it is that you should only be using the getActivity in certain fragment functions(Based on the fragment life cycle). This might mean that you have to move some of your logic to other functions.