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.
Related
Is it normal behaviour that onDestroyView() is called immediately when you navigate to a different fragment?
I call the following method to navigate:
findNavController().navigate(R.id.action_homefragment_to_detailsfragment)
Is there a way to avoid this? Or should everything be restored from a ViewModel? For example my last scroll position in a ScrollView..
Fragments on the back stack have their views destroyed, but the view state is saved and automatically restored when it comes to the top of the stack again (i.e., you hit the system back button).
Only views with an android:id have their state saved and restored, so make sure any important views have an id.
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
Background:
I have a main Activity, it wraps a main Fragment that can be changed, and in order to keep a backstack I use FragmentManager's backstack.
The main difference from keeping an activity stack is that when a fragment is pushed to the backstack and get replaced it will call it's onDestroyView() but not it's onDestroy(), and when it get back it's view will be re-created with onCreateView(). (however onCreate() is not called as the fragment object is not disposed)
In an activity stack it won't happen and the views remain.
This has a positive effect on low-end devices as the Android OS can free some memory and you don't have to keep the views right (in my app messages from the server might change the view in any time) so one can save precious bandwidth as well.
The Actual Problem:
Let's say I have a fragment and the user click on something and it's view is changed, e.g. a list is expanded.
If the user then go to another screen (i.e. fragment) the previous fragment will be pushed to the backstack and it's view will be destroyed.
When the user is going back, the fragment will be re-created and will not "remember" the changes the user had made, e.g. the list would not be expanded as it should
so how can I save the state and restore it without making special cases for every view?
Undesired Answers:
keep the view alive: doing something to keep the view would break the fragment efficiency
using onSaveInstanceState(): it will not get called when the fragment is pushed to the backstack as the activity is not destroyed and that's not a configuration change.
special object: prefer not to do it if there is a way the system can do it for you.
When using FragmentActivity it automatically restores fragment state and recreates all fragments.
I know this is done mainly saving the state in onSaveInstanceState and then restored in activity's onCreate. Looking a little on the code I've seen that all fragments are recreated (or only attached if retainInstance is true) and added to the FragmentManager but it's not clear to me in which way they are added to the view, because view isn't automatically restored.
My original problems were that I get duplicates of some fragments similar to that other question.
I workarrounded that in onCreate with:
Fragment f = fm.findFragmentByTag(tagName);
if(f==null) {
f = createFragment();
fm.beginTransaction().add(R.id.myContainer,f,tagName).commit();
} else {
//Nothing it's on the view
}
Now it works, but I still doesn't understand completely how it works.
My doubts are:
In which moment and how are fragments attached to the View? I've experimented that fragment restoration is done in onCreate of FragmentActivity. But if I call setContentView after that, how the fragment attach to the view?
Can I prevent fragment recreation without overwriting onSaveInstanceState? Because due to different orientation layouts with different number of fragments my original intention was to recreate only one state fragment marked as retained an don't restore the other view fragments that are not marked as retained.
View hierarchy in not restored automatically. So, in Fragment.onCreateView() or Activity.onCreate(), you have to restore all views (from xml or programmatically). Each ViewGroup that contains a fragment, must have the same ID as when you created it the first time. Once the view hierarchy is created, Android restores all fragments and put theirs views in the right ViewGroup thanks to the ID. Let say that Android remembers the ID of the ViewGroup on which a fragment was.
This happens somewhere between onCreateView() and onStart().
I think it could be possible to keep fragment recreation but, on the ViewGroup that hold the fragment, set visibility to GONE. In this way, the fragment doesn't appear and you can remove it programmatically later.
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!