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.
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 am using the Fragment class with the Android support library v7. In my Activity's onCreate() method I create a bunch of fragments and store them into properties of my activity.
this.firstFragment = new FirstFragment();
this.secondFragment = new SecondFragment();
// and so on
I use the navigation drawer pattern to switch between the fragments of my app. To change the active fragment I use following code.
// Replace the current content of the fragment holder with the selected section fragment.
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.container, selectedFragment).commit();
This results in a call to onDestroy() of the removed fragment. Is it safe to reuse the fragment after it's onDestroy() has been called, or should I recreate the fragment every time it is shown to the user.
This is a question of time vs. memory consumption, as at least one of the fragments needs some time to get created.
onDestroy functionality is to destroy all the used variables and consumed memory. All those will be flagged as a Dummy data to enable the garbage collector to remove them from the memory whenever it is needed.
Calling the Fragment again after calling onDestroy will pass through the lifecycle again from the beginning through onCreate and all the variables and local object will be re-initialized again.
Is it Safe? Yes, it is safe.
You are thinking deeper to handle the lifeCycle of the Fragment that is already being handled by the OS.
If you want to prevent the Fragment from being destroyed you can create it as a static object.
The Fragments developer guide seems to tell no, according to the lifecycle diagram.
If you want to keep your fragment for a later use, i suggest you use FragmentTransaction.detach(Fragment) or FragmentTransaction.hide(Fragment) instead of FragmentTransaction.remove(Fragment).
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 have an app that has one main Activity that swaps out numerous Fragment's. Well it doesn't matter what Fragment you are on, after low memory kills the Activity and you try to return to the app, it boots you back to the "start" Fragment that the Activity first calls. (Note: Almost all of these are actually ListFragment's)
So here are my questions:
Should I be using onSaveInstanceState() in EACH Fragment? And if so, am I saving the Data in the Fragment OR the Fragment itself? Or do you use onSaveInstanceState() only once in the Main Activity. (If this is even the course to take)
Note: I have setRetainInstance(true) but I don't think I am handling that correctly, if that is the solution. These are all put as the last line of onActivityCreated().
The answer depends a lot on how you are managing fragments.
I'll assume you are not using the Fragment backstack, and that you have called setRetainInstance(true) on EACH fragment.
You need to use a tag when you attach the fragments.
In Activity#onSaveInstanceState() you need to remember which fragments are visible.
In Activity#onCreate you need to find the existing Fragments by tag for each fragment, then create new instances of any Fragments you can't find. Now you can use the information from the saved instance state to make the appropriate Fragments visible (show or add or replace as necessary depending on how your code manages the fragments.)
Edit in response to questions/comments:
activty.getFragmentManager().findFragmentByTag(tag); finds an existing fragment
in a Fragment transaction: add(fragment, tag), replace(id, fragment, tag), etc. lets you specify the tag. You can also put it in a layout file using the attribute
class=".myFrag$tag"
The actual fragment object including its contents still exist when you use setRetainInstance.
Note: If you don't want to use tags, you may also use the fragment manager's putFragment/getFragment methods to put the fragment into the instance state bundle.
Finally you can simply let the fragment save itself by calling FragmentManager's saveFragmentInstanceState but I've had trouble using this correctly.
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).