In the main part of my application I have 2 fragments open at once. Only one is shown at once though.
The second fragment is being created from the onCreateView method of the first fragment, as the second fragment is used to control the first fragment.
Bundle args = new Bundle();
args.putInt(ReferenceSelectorFragment.ARG_TAB_INDEX, Tab.BOOK.position);
mReferenceSelectorFragment.setArguments(args);
fm.beginTransaction()
.add(container.getId(), mReferenceSelectorFragment, FRAGMENT_TAGS[0])
.hide(mReferenceSelectorFragment)
.commit();
I'm properly hiding and showing the fragments using a fragment transaction:
getActivity().getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
.hide(ReaderFragment.this)
.show(mReferenceSelectorFragment)
.addToBackStack(null)
.commit();
This works wonderfully, as expected, except for one tiny flaw. When the application is minimized for a while and the OS closes the process, when the application is opened again, trying to restore the previous state, both fragments are restored on top of each other. Meaning both are seen at the same time on top of each other. (This can be replicated by pressing the home button on the app, and then using DDMS to kill the running process).
I've tried everything to detect this and fix this, but it's proven very difficult to programmatically distinguish between a rotation and the application being restored after the process has been exited. What is the proper way to deal with the lifecycle of showing and hiding fragments?
This might help you out: https://stackoverflow.com/a/13306633/969325
Basically you could pass a null bundle in your activities super.onCreate(savedInstanceState); or only create new fragments when they doesn't already exists. I suspect that is your issue.
Related
Actual problem / TL;DR: Except when using one way I consider a hack, I can't pop the activity's entire backstack to return to my initial state with a single fragment added during onCreate(). The Fragment is present in the Activity State Manager dump, but is not visible, leaving me with an empty screen.
I have encountered this issue today and am mostly trying to understand whether this is caused by a bug or by my misunderstanding of the FragmentManager's BackStack. I want to make sure I'm not misusing Fragments or building on shaky API foundations.
I have an activity which essentially offers a descending "tree" navigation as a grid of buttons where each button opens either a sub-grid (e.g. sub-category with more buttons) or some custom form. The content of the forms is irrelevant, but users are expected to repetitively fill them in various orders.
I use Fragments from the support library (support-v4:25.1.1) and whenever a new "screen" is required (either for a form or a sub-grid) it will be added using a transaction on the activity's FragmentManager similar to:
/* adding a new screen, going further down our nav tree */
fragmentManager
.beginTransaction()
.addToBackStack("...")
.replace(R.id.container, newFragment)
.commit();
Every transaction looks like that except the setup of the initial state of my activity, which adds the initial "root grid" fragment without adding the transaction to the backstate.
/* adding the initial fragment during the first execution of activity's onCreate(). */
fragmentManager
.beginTransaction()
.replace(R.id.container, rootFragment)
.commit();
The reason I'm posting here is that I encountered a very strange behaviour when attempting to pop the entire BackStack, which is supposed to bring the user back to the initial state of the activity which has the root grid (the one added in the code above). Almost every technique I tried effectively clears the backstack, but leaves an empty screen instead of my initial main-menu grid. After looking around on multiple questions here and the docs, I tried multiple solutions and only this hack-looking one has worked:
int remainingEntries = fragmentManager.getBackStackEntryCount();
while (remainingEntries-- > 0) {
fragmentManager.popBackStackImmediate();
}
I say this seems like a hack because it requires me to immediately (synchronously) pop an arbitrary number of backstack entries to reach the root, whereas from my understanding it should be possible to do that with a single asynchronous method call:
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
or as I've also seen posted around here:
int id = fragmentManager.getBackStackEntryAt(0).getId();
fragmentManager.popBackStack(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
This is only an issue with the root fragment, probably because the transaction which adds it to the view is not added to the backstack, since that would cause an extra empty screen to be shown when the user presses the back button to close the activity.
More details:
According to the Activity State Manager dump my fragment still has its view, but has both mFragmentId and mContainerId set to #0.
I do add another fragment I haven't discussed here which retains its instance, but it is only used to hold some data and has no view whatsoever.
There is a PopBackStackImmediate bug in support library version 25.1.0 and above.
Since this issue has been reported either you have to wait for solution, Or you can downgrade the support library version to 25.0.1 and get the expected behavior.
UPDATE
Looks like this bug has been resolved in support library version 25.3.1. Update your library to version 25.3.1.
I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.
I am hitting a very strange problem in Android and I can't figure out why it's happening or how to code around it. I truly believe this to be an Android bug.
I have a MainActivity which contains a FrameLayout named main_container (its height and width are both match_parent as each fragment should be the only fragment "showing" to the user). From MainActivity, I add Fragment A like so:
mFragmentManager.beginTransaction()
.replace(R.id.main_container, frag, fragTag)
.commit();
From there, Fragment A, upon a user's click of a view, will add Fragment B like so ("frag" and "fragTag" are different values than the above code snippet):
mFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
.add(R.id.main_container, frag, fragTag)
.addToBackStack(null)
.commit();
And from here, Fragment B will add Fragment C like so (again, "frag" and "fragTag" are different values than the previous two snippets):
mFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
.add(R.id.main_container, frag, fragTag)
.addToBackStack(null)
.commit();
So at this point, in the backstack, we should have Fragment A -> Fragment B -> Fragment C.
Fragment C invokes the MediaPicker upon the user's click of a view. Doing so calls all fragments' onPause methods and the app is put in the background. Now when the user selects an image, the application is resumed but here's where the bug happens... it resumes in this order, as proven with breakpoints in each fragments' onResume method:
Fragment A -> Fragment C -> Fragment B
This causes all sorts of issues because each of my fragments registers itself as a listener in the MainActivity to handle back button clicks. This logic relies on that ordering being correct. For some reason, it's still showing Fragment C on top, but onResume was definitely called out of order.
Perhaps even worse though... rather than clicking for MediaPicker, you can simply rotate the phone to cause a config change. This exhibits the same behavior of reordering to A -> C -> B but in this case it DOES actually show the wrong fragment on top. It SHOWS Fragment B on top.
Is it a design point that you can't rely on Android to resume fragments in the same order you added them to the backstack and I'm supposed to code around it? Or am I doing something wrong? Or is this really an Android bug? I am by far not a newbie to Android development, but this one has me stumped.
EDIT:
I've pinpointed what is going on and apparently it's by design. It seems pretty crazy to me and I disagree with the logic behind it. I may be able to fix this with reflection, but I don't like doing that. Anyways, on to the problem.
The problem is with the way FragmentManagerImpl keeps track of active fragments. It has an ArrayList to keep track of active fragments and when everything is paused (such as in my case where I'm starting an intent to get a photo from media gallery, thus it's leaving my app), upon resuming back into my app, it moves the fragments back to active in the same order they're in that ArrayList. Sounds great, eh?
Well here's my problem. When things are taken OUT of that ArrayList, they don't remove() the item, they just set it to null and then have logic to reuse that empty "slot" (line 1168 in the github link) when the next fragment comes along. In my case, the transient fragment that leaves a hole in the ArrayList is a DialogFragment. Putting it back into terms of my original report, Fragment A shows a DialogFragment... clicking a certain button in that DialogFragment brings up Fragment B. Clicking another view in Fragment B brings up Fragment C. But here's what happens to the ArrayList FragmentManagerImpl keeps track of after clicking the button in the DialogFragment:
{ FragA, null (used to be DialogFragment), FragB }
So apparently DialogFragment was moved out of active state after FragB was moved to active, thus leaving a hole. So now we click the view in FragB to bring up FragC and the ArrayList looks like so:
{ FragA, FragC (reused DialogFragment's slot), FragB }
We go off to the media picker, come back, and voila the fragments are resumed out of order with respect to how I instantiated them in the first place. This makes no sense to me and if you don't step into OS code with breakpoints, you never figure out why Android is not behaving the way you told it to. Seems like it would have been easier to just do an ArrayList.remove() of the fragment you removed, thus leaving no holes.
Like I said, I can probably get around this with reflection... but I'm leery of that because there is also this mIndex variable in all Fragments that corresponds to the index of it's slot in that ArrayList (mActive). So I'd have to be sure to keep those in sync... and now I have a dependency on knowing how the OS code works. :(
This is a known issue. Google "android fragment reordering" and you will get a whole page of links on the subject including some solutions.
I have an app setup that uses fragments in various place, in one case a fragment exists and upon pressing a button another replaces it using replace() all normal so far, however when the user presses the android back button and the new fragment is removed/popped (whatever the system uses) and the app returns to the first fragment there is no event being fired that i can override to perform an action.
is this normal?, the docs seem to suggest that onResume should be fired in this instance, other sites don't, and my app doesn't, if anyone has any clue if this is normal behaviour or not that be great and if it is what event can i hook into?
Edit: i forgot that all of this is occuring as nested fragments, the first fragment is the child of another so is added using a child fragment manager the second replaces the first using its regular fragment manager, besides the issue im describing this is working great
how the first fragment is originally put in place
Menu_Fragment menufragment = new Menu_Fragment();
getChildFragmentManager().beginTransaction().replace(R.id.menu_container, menufragment, "_menu_fragment").commit();
How the second fragment is "added"
Google_Map_Container_Fragment mapcontainerfrag = new Google_Map_Container_Fragment();
getFragmentManager().beginTransaction().replace(R.id.menu_container, mapcontainerfrag, "addedmap").addToBackStack(null).commit();
I am having a pretty big issue and I am not quite understanding what is happening. I am developing an application that uses Fragments (from the support library) and am using FragmentTransaction.replace() to place new Fragments on to the back stack and replace the old one. The code looks as follows:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = ft.beginTransaction();
// Animations in my res/anim folder
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
This is successful in replacing my fragment. My issue is the following. In one Fragment, I have a list of items that is built from user input. Now, when the user clicks next and then clicks the back button (to return to the list), the list is empty because the view is destroyed. Now, I have noted the following:
onSaveInstanceState is not called. I believe this is because that is only called when the parent Activity tells it to. Based on the docs: " 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.". Apparently, performing a replace on the FragmentTransaction is not one of those times. Does anyone have confirmation on this or a better explanation?
setOnRetainInstanceState(true) is not helpful in this situation. Again, I believe this has to do with info from the docs: "Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)". I am not performing any action in re-creating the activity so this is of no use.
So, I guess my main question is: is there a way to preserve the View state (simply retain the Fragment) when using replace? There is FragmentTransaction.add(), but there are a few issues with this as well. One being that the exit animation is not performed, thus the animation is not correct. Another is that the new Fragment that the old fragment (the one that is being put into a non-visible state) is still clickable. For example, if I have a ListFragment, and I place a content fragment on top of that by using add, I can still click the list items in the ListFragment.
Without being able to see the code of your fragments this is a bit of a guess, but in the past I've run into this same issue and I've found that resetting the adapter in your ListFragment in onViewStateRestored seems to do the trick.
public void onViewStateRestored (Bundle savedInstanceState)
{
super.onViewStateRestored (savedInstanceState);
setListAdapter(new ArrayAdapter(Activity, R.layout.nav_item, objects));
}
Which is weird considering the documentation states that this method is called after onActivityCreated but before onStart. But it seems that it is also called at other times because when the most recent fragment transaction is popped off the back stack this method is called before the previously replaced fragment is displayed. The activity that owns the fragments has not been paused or obscured in any way, so according to the docs onViewStateRestored should not be called since just the fragments were modified. But this seems to work anyway.
It sounds like you simply need to make sure you have properly implemented onCreateView and onDestroyView. The situation you are describing seems to indicate that when the list fragment is put on the back stack (as a result of the replace transaction) Android is calling onDestroyView to free up some resources. However, it apparently has not destroyed the list fragment because when you tap back you are getting back the same instance of the fragment.
Assuming this is all true then, when the user taps back Android will call onCreateView. Any state that you have stored in the fragment's instance variables should still be there and all you need to do is repopulate the view...perhaps set the adapter on the ListView or whatever.
Also make sure your onSaveInstanceState() callback actually does save any instance state that you need to rebuild the view. That way if the fragment actually does get completely destroyed the FragmentManager can restore the state when it needs to recrete the fragment later.