I'm using a ViewPager with a FragmentStatePagerAdapter and I'm having problems saving the Fragment's state across orientation changes. It works fine when paging back and forth e.g. viewing a page, swiping two pages away, and then going back 2 pages to the original Fragment correctly saves and restores state. I am doing this using onSaveInstanceState and restoring state in onCreateView if the Bundle isn't null.
Changing orientations, however, doesn't work through the same mechanism and from my testing doesn't even call the fragment's onSaveInstanceState method.
Is this expected? Am I missing something to force it to save instance state? Did I do something to stop it from working?
Thanks!
Turns out I wasn't calling super.onSaveInstanceState() in the Activity so none of the ViewPager/Fragment state was maintained. Adding in super.onSaveInstanceState() fixed it!
Related
CONSTRUCTION
I've got Activity that holds a "path" made of Fragments.
The User goes from Fragment1 to Fragment2 and then to Fragment3 filling up the required informations. Nothing fancy here just plain backStack and .replace made on Fragments.
QUESTION
How should I retain that information when orientation changes to get my backStack back the way it was before orientation change?
IDEAS
The only idea I have is to insert into saveInstanceState the last visible Fragment and recreate things from there but it feels really hacky and I think in the long run it's going to make some major problems.
/////////////UPDATE//////////////
It looks like nowadays Android is capable of doing it on its own as long as you are following the guidelines provided by ARTICLE
The backstack itself is saved and loaded automatically with the activity.
Here's an article how Android does save and load activity and fragment states.
What you should care of is implementing the same logic of saving and restoring states for each fragment.
I wonder if there is any advantage of fragments, on screen rotation.
Generally fragments get destroyed followed by activity. Is there something that fragments retain while doing so?
onDestroy() method is called both in the activity and fragments.
I can try to figure out advantage of Fragment on Screen rotation.
Realtime app problem is:
Android is the potentially frequent destruction and reconstruction of an Activity. The most common time this occurs is when the user rotates the device between horizontal and portrait orientations (Screen rotation).
This crashing usually occurs because device orientation changes cause the Android framework to tear down the displayed Activity along within any contained Views, and then to fully reconstruct the Activity/View hierarchy. Any references to the Activity or to the Views within the Activity suddenly become invalid. Similarly any references within the Activity or Views that were set as a result of a user action or similar are now lost.
There are a number of ways to deal with this issue but one of the easiest is to take advantage of Fragments.
Things to keep in mind:
Fragments won’t automatically resolve this issue because, by default, when the Activity is torn-down in response to an orientation change the Fragment contained within the Activity is also torn down along with any contained Views.
The solution lies in an underused method: Fragment.setRetainInstance with a value of true.
why?
Calling setRetainInstance with a value of true causes Android to preserve the Fragment across the teardown/reconstruction cycle of an Activity. Along with the Fragment, the Views or other object references contained within the Fragment or Views remain.
With setRetainInstance(true) called on a Fragment instance.when an orientation change occurs, Android…
Holds a reference to the Fragment instance
Tears down the old Activity instance
Creates a new Activity instance
Attaches the preserved Fragment instance to the new Activity instance
you must add
android:configChanges="keyboardHidden|orientation|screenSize"
in parent Activity also calling
setRetainInstance(true)
in onCreate of fragment
In my program, I have the following hierarchy:
Activity
Fragment
ViewPager + FragmentStatePagerAdapter
Fragment containing video
The fragment that is nested immediately in the activity is initialized using setRetainInstance(true). This led to a crash whenever switching orientation. Used the solution described in the bug report: https://code.google.com/p/android/issues/detail?id=42601#c10.
Still, the app would crash whenever I would switch orientation. I found another bug report + solution: https://code.google.com/p/android/issues/detail?id=42601#c32.
After applying this solution I was able to rotate the device without the app crashing. However, the deeply nested fragment did not retain its state. As this fragment contains a video that should continue playing in spite of any orientation change, this is a must-have. I found a third bug report here describing this is a known issue in android support library versions 20 an up: https://code.google.com/p/android/issues/detail?id=74222#c17
Now, it does seem that the deepest fragment is retaining some kind of state. At least it is not destroyed, since the audio of the video keeps playing throughout and after the orientation change. However, the fragment is not restored after the orientation change. I simply see a white rectangle where the video should be.
As you can see, this is getting ridiculous. I have already had to use three hacky solutions to bugs in the ViewPager class, and it is still not working properly. If anybody has any idea what else I can try, I will be very much obliged.
Eventually, I did not find a solution to the problem. I have solved the problem by retaining the state of the nested fragments in the parent fragment, which does correctly retain state. To do this, I did the following:
Add a HashMap to the parent fragment
Gave every fragment a unique ID string that could be restored whenever the fragment was recreated
Created a state object for all nested fragments in their onCreate method and added it to the HashMap in the parent fragment. Or, if ((ParentFragment)getParentFragment()).stateMap.contains(id), restore the existing state.
Moved all member variables of the nested fragment to the StateObjects
I have a tab host control that is loading tabs using fragments.
Each time a tab is switched it detaches the old fragment and attaches the new fragment.
I noticed that the OnCreateView method is called during this process, and that a lot of my state is getting lost since it recreates the view each time. However I noticed that some view state such as the value of edit text is being maintained across detach/attach.
I'm wondering how Android is automagically restoring state when the view is being completely destroyed and recreated as a new view. The value of the Bundle savedInstanceState is always null when I am just switching tabs. Bundle savedInstanceState only becomes populated when I do something like rotating the screen.
as far as I can tell this restoring of state is taking place just before the fragment onStart method is called.
When attaching and detaching fragments only the views are destroyed, the fragment instance remains the same.
The fragment manager restores the states of views that have ids, and the savedInstanceState is null.
In case of rotation, the fragments are probably recreated by you somewhere else (in activity's onCreate()?).
When a fragment is about to be removed from the window (or replaced) its onSaveInstanceState(Bundle) (or onRestoreInstanceState(Bundle)) method is called. This will propagate through the fragments hierarchy restoring its previous state.
I'm not quite understanding this fragment lifecycle business.
I have a pretty standard 3 page horizontal slider view Pager layout for a "view details" section of my app. I start my app on the middle page of the three. My FragmentActivity sets the page to page 1.
mPager.setCurrentItem(1); //default to center page, current data view fragment
I've been using the FragmentStatePagerAdapter because using the FragmentPagerAdapter crashed my app at times, when coming back from a suspended state for example, and this was the quickest way to work around that for now. Seems to work but I suspect the State aspect is what might be the cause of my problem here, maybe.
So at first I thought that I would have each fragment do the work of getting data from a rest service and then showing it in a list, but I'm not so sure any more.
I tried running a unique async task to fetch data in each of the fragments onCreateView events.
Then after reading more on the fragment lifecycle I switched to onCreate, having noticed that the onCreateView was being called quite heavily, which in turn made for a greedy app that too often requested data over the wire.
Switching to onCreate hasn't changed anything. OnCreate is still geting called just as much as onCreateView for the 2 adjacent fragments.
The odd thing is that the fragment that I set to be the first one to display in Fragment Activity only gets the onCreate called the one time.
Something doesn't feel right here.
Right now I'm thinking that I should have the parent fragment activity declare and call all the async tasks to fetch the data that I need to display in the fragments.
Set the results of the async calls in an object owned by the parent fragment activity and then have the fragments use the object contained by the parent to create the list view etc.
But what if the async tasks started by the parent activity don't finish before each fragments onCreateView is called and the object contained by the parent isn't ready yet.....
signed, confused and frustrated
ViewPager is quite zealous in shutting down things it isn't currently using, and this is exactly what is happening here. The default behaviour is for ViewPager to "keep around" one page either side of the page being viewed, and destroy the rest. Hence in your 3-page view, page 3 gets destroyed when selecting page 1, then when page 2 is reselected page 3 is recreated. As you've noticed, page 2 only has onCreate(..) called once because it is always adjacent to, or is, the currently selected page.
To solve this, simply set ViewPager.setOffscreenPageLimit(2). Then the ViewPager will keep all your Fragments. Obviously this isn't a good idea for a large number of Fragments, but for your case it should be fine.
#Espiandev's solution will work for your current case, but you're right that your state is the issue. You should use the setArgument and/or onSaveInstanceState methods to save your Fragment's state (which shouldn't be too hard, since e.g., a response from the server can usually be represented as a String), and then use getArgument and/or the Bundle passed in onCreate to restore it.
Alternatively, you could have your Activity do the server fetches, and then call setArgument for each of your fragments, and check the arguments inside your Fragment to determine if your data has arrived yet (and if not, potentially display a loading state of some kind).
If you care at all about screen orientation change, this related question will also be useful to you.