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).
Related
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
I was reading Handling Configuration Changes docs.
The document advises to use Fragments that have setRetainInstance set to true and then recover the fragment via the fragmentManager's findFragmentByTag method.
My question is that when the activity is destroyed will the fragmentManager survive that? Is it like sharedPreferences where the values stored in it are unaffected by what happens in the activity as long as the values are committed?
No. When you set setRetainInstance(true) within a fragment's onCreate, (with fragment tag, say "my_fragment"), when your parent activity orientation changes, the android framework stores the instance for the fragment as long as the activity is not destroyed. When you save the fragment tag variable in the parent activity and restore it (see example: https://stackoverflow.com/a/47823139/7152359), you can again call getSupportFragmentManager()... and set the fragment using the tag "my_fragment" that you stored.
In simple words, setRetainInstance(true) is only used to help developers not go through complex/long procedures of onSaveInstanceState(..) and onRestoreInstanceState(..) like many a times developers have to do for activities.
From my investigations today I believe that FragmentManager does actually survive activity destruction due to configuration changes. This is because if you add a fragment with a tag to the fragment manager (for example, fragmentManager.beginTransaction().replace(R.id.container, myFragment, "blah").commit()), then you can still retrieve that fragment with fragmentManager.findFragmentByTag("blah") even after the activity has been destroyed and re-created due to a configuration change. You can see from the source code that it retains a ArrayList<Fragment> mAdded which contains all of these previously added fragments. If the FragmentManager was destroyed and re-created, this mAdded would have become an empty list, which clearly isn't the case as above.
HOWEVER, the nature of what is retained within each fragment depends on setRetainInstance. If you don't set retain, then only the fragment arguments and saved instance state are persisted, and the fragment instance is re-created by the framework. If you do set retain, then the whole instance (including fields) is persisted. However either way, the FragmentManager itself is still persisted otherwise we wouldn't be able to retrieve tags from it anymore.
While the persistence of FragmentManager across configuration changes is not explicitly mentioned by the documentation, I believe it's implied by statements such as the following:
during a configuration change, your activity and all of its fragments are destroyed and then recreated with the most applicable Android resources. The FragmentManager handles all of this for you. It recreates instances of your fragments, attaches them to the host, and recreates the back stack state.
i.e. if the FragmentManager was not persisted across the configuration change, it could not continue to manage this after activity re-creation.
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.
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)
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).