For restoring the state of the activity after it is recreated (for instance after the screen-orientation change) I implemented onSaveInstanceState() and onRestoreInstanceState(). It is simple to save/restore simple information like int, double etc. But what about saving/restoring objects like Timer?
You cannot store "live" objects (like db connections) in the Activity arguments or saved instance data. Those mechanisms are designed so that the application can be completely stopped, so it only works with things that can be "serialized" and later restored.
What you can do is use fragments. If you add a fragment without UI (check here, look for
“Adding a fragment without a UI”) and call on it setRetainInstance(true) the fragment will get reattached to the activity, surviving any configuration change.
Hope it helps. (Remember you can use fragments with old Android versions by using the support package)
Related
I find Fragment#setRetainInstance(true) confusing. Here is the Javadoc, extracted from the Android Developer API:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Question: How do you as a developer use this, and why does it make things easier?
How do you as a developer use this
Call setRetainInstance(true). I typically do that in onCreateView() or onActivityCreated(), where I use it.
and why does it make things easier?
It tends to be simpler than onRetainNonConfigurationInstance() for handling the retention of data across configuration changes (e.g., rotating the device from portrait to landscape). Non-retained fragments are destroyed and recreated on the configuration change; retained fragments are not. Hence, any data held by those retained fragments is available to the post-configuration-change activity.
It's very helpful in keeping long running resources open such as sockets. Have a UI-less fragment that holds references to bluetooth sockets and you won't have to worry about reconnecting them when the user flips the phone.
It's also handy in keeping references to resources that take a long time to load like bitmaps or server data. Load it once, keep it in a retained fragment, and when the activity is reloaded it's still there and you don't have to rebuild it.
Added this answer very late, but I thought it would make things clearer. Say after me. When setRetainInstance is:
FALSE
Fragment gets re-created on config change. NEW INSTANCE is created.
ALL lifecycle methods are called on config change, including onCreate() and onDestroy().
TRUE
Fragment does not get re-created on config change. SAME INSTANCE is used.
All lifecycle methods are called on config change, APART FROM onCreate() and onDestroy().
Retaining an instance will not work when added to the backstack.
Don't forget that the above applies to DialogFragments as well as Fragments.
The setRetainInstance(boolean) method is deprecated, use ViewModels instead.
The setRetainInstance(boolean) method on Fragments has been deprecated as of Version 1.3.0 of fragment API.
With the introduction of ViewModels, developers have a specific API for retaining state that can be associated with Activities, Fragments, and Navigation graphs. This allows developers to use a normal, not retained Fragment and keep the specific state they want retained separate.
This ensures that developers have a much more understandable lifecycle for those Fragments (one that matches all of the rest of their Fragments) while maintaining the useful properties of a single creation and single destruction (in this case, the constructor of the ViewModel and the onCleared() callback from the ViewModel).
I do know how to save data between config changes (using onSavedInstanceState and checking the Bundle in onCreate, for example, for not being null) and I also do know how to make data persistent (via SharedPreferences), but here I need an in-between-solution.
I have a resource-heavy fragment which gets killed (onDestroyView() and onDestroy() are being called) when I replace it with another resource-heavy fragment. When I return to the former, its state is gone (the Bundle is null), making it persistent would save state across "sessions" which contradicts state-saving in other fragments. Is there a way how have "session-scoped" persistence in Android? By "session-scoped" I mean a period longer than the Android life-cycle and shorter than "forever".
So, there are a bunch of ways of saving the fragment and activity states.
If you have several fragments and an activity, and you are simply replacing the fragment view with those fragments then you may use graph level ViewModel. Check out navigation architecture component.
If you just want to save the state of the fragment, just create ViewModel for fragment view and make its lifecycle as activity lifecycle. So the ViewModel does not get cleared or killed until the host activity for the fragment is destroyed.
You may check out view model here in Android Documentation: ViewModel
Here is the good comparison for managing saved states, which options to consider 1) ViewModel, 2)Saved states or 3) Persistence storage
Depending on the size of data and context you may choose one of the given options. I would fully recommend reading it. Here is the link
Saving UI States
Android Architecture Components provide the LiveData and ViewModel classes which are more lifecycle-friendly and designed for a leaner Activity/Fragment. These classes handle storing data across configuration changes, but I'm confused about their use compared to the Activity framework APIs. Are onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) still necessary or useful for preserving activity state?
onSaveInstanceState & onRestoreInstanceState is still useful.
ViewModel holds data only when process is alive.
But, onSaveInstanceState & onRestoreInstanceState can hold data even if process is killed.
ViewModel is easy to use and useful for preserving large data when screen orientation changes.
onSaveInstanceState & onRestoreInstanceState can preserve data when process is in background.(in background, app process can be killed by system at anytime.)
Assume a scenario :
user is in activity A , then navigates to activity B
but because of low memory Android OS destroys activity A , therefor the ViewModel connected to it also destroys. (You can emulate it by checking Don't keep activities in Developer options)
now user navigates back to activity A, Android OS try's to create new Acivity and ViewModel objects. therefor you loosed data in ViewModel.
But still values in savedInstanceState are there.
As well as the other answers which talk about the ViewModel's persistence beyond simply configuration changes, I think there are a couple more use cases:
Performance reasons
Sometimes you don't want to store all of the latest values of view attributes in the ViewModel for performance reasons. You may have greater need to save them when the view is being re-created. For example, user's scroll position on a view within your activity/fragment. You probably don't want to save the scroll position every time the user scrolls. But you might want to save that onSaveInstanceState so you can restore that when the view is recreated (onRestoreInstanceState).
Initialization to perform after restore
Some views may require initialization especially because of the restore, due to the complex design of those views not being able to save everything. For example, I had a WebView and if the user was in the middle of loading a page during a configuration change, I want the WebView to try to load the new page (rather than the old one). After restoring the state, the observers of LiveData will get the latest values but this doesn't help much with something like this (I only want the view to load a page from the ViewModel at the point of restore, not at other times). So we just do that initialization via the restore state.
Final word
With all this stuff I would advocate keeping your onSaveInstanceState and onRestoreInstanceState as simple as possible. Ideally just call a method on the ViewModel and that's it. Then we can extract all of the logic from the view into the ViewModel, and the view is just left with boilerplate code.
Since orientation changes happen fairly quickly one would think that keeping a Fragment in memory during that time would be more efficient than recreating it again.
Since it's kept for a short time only, there seem to be little impact on memory.
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
Google's primary concern is that you'll screw up and have data members in the fragment that refer to the old activity that you do not clean up in post-configuration lifecycle method calls (e.g., onCreateView()). For example, you might hold onto a widget in a data member, where you do not immediately null out or repopulate that data member on a configuration change. If your fragment has a reference back to the old activity, the old activity (and everything it holds onto) cannot be garbage-collected until your fragment gets destroyed. This is one of the reasons why Google does not recommend retaining any fragment with a UI.
Firstly maybe check this link: http://android-er.blogspot.com/2013/05/how-setretaininstancetrue-affect.html
Basically you shouldn't always retain the instance of the fragment because fragments are attached to activities. Hence when an activity is re-created (i.e. on configuration change), the fragment needs to be re-associated to the new activity (which leads to extra coding and some extra problems). If you just unnecessarily setRetainInstance(true), you are giving yourself more error checking and coding to do for no reason. By setting the setRetainInstance(true), you will need to deal with a different fragment lifecycle as well because certain methods in the lifecyle are now skipped (i.e. the onCreate() is no longer called after configuration changes). As far as I understand, setRetainInstance(true), won't make it more efficient because you could use onSaveInstance to save any data that you would want to use in the recreation of the same kind of fragment.
I hope this helps.
I find Fragment#setRetainInstance(true) confusing. Here is the Javadoc, extracted from the Android Developer API:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Question: How do you as a developer use this, and why does it make things easier?
How do you as a developer use this
Call setRetainInstance(true). I typically do that in onCreateView() or onActivityCreated(), where I use it.
and why does it make things easier?
It tends to be simpler than onRetainNonConfigurationInstance() for handling the retention of data across configuration changes (e.g., rotating the device from portrait to landscape). Non-retained fragments are destroyed and recreated on the configuration change; retained fragments are not. Hence, any data held by those retained fragments is available to the post-configuration-change activity.
It's very helpful in keeping long running resources open such as sockets. Have a UI-less fragment that holds references to bluetooth sockets and you won't have to worry about reconnecting them when the user flips the phone.
It's also handy in keeping references to resources that take a long time to load like bitmaps or server data. Load it once, keep it in a retained fragment, and when the activity is reloaded it's still there and you don't have to rebuild it.
Added this answer very late, but I thought it would make things clearer. Say after me. When setRetainInstance is:
FALSE
Fragment gets re-created on config change. NEW INSTANCE is created.
ALL lifecycle methods are called on config change, including onCreate() and onDestroy().
TRUE
Fragment does not get re-created on config change. SAME INSTANCE is used.
All lifecycle methods are called on config change, APART FROM onCreate() and onDestroy().
Retaining an instance will not work when added to the backstack.
Don't forget that the above applies to DialogFragments as well as Fragments.
The setRetainInstance(boolean) method is deprecated, use ViewModels instead.
The setRetainInstance(boolean) method on Fragments has been deprecated as of Version 1.3.0 of fragment API.
With the introduction of ViewModels, developers have a specific API for retaining state that can be associated with Activities, Fragments, and Navigation graphs. This allows developers to use a normal, not retained Fragment and keep the specific state they want retained separate.
This ensures that developers have a much more understandable lifecycle for those Fragments (one that matches all of the rest of their Fragments) while maintaining the useful properties of a single creation and single destruction (in this case, the constructor of the ViewModel and the onCleared() callback from the ViewModel).