Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would I ever want `setRetainInstance(false)`? - Or - The correct way to handle device rotation

Please correct me if I'm wrong on any of this. This is a kind of clarifying question since I haven't seen it explicitly written anywhere.

In Android 4, you can call setRetainInstance(true) on a Fragment so that on configuration changes (which basically means device rotation), the Fragment java object isn't destroyed and a new instance of it isn't created. That is, the instance is retained.

This is much more sane and less infuriating than in Android 1-3 since you don't have to deal with onRetainNonConfigurationStateInstance() and bundle up all your data so it can be passed to the new Fragment (or Activity) instance only to be unbundled again. It's basically what you would expect to happen, and arguably how it should have worked for Activitys from the beginning.

With setRetainInstance(true) the view is also recreated (onCreateView() is called) on rotation as you would expect. And I assume (not tested) that resource resolution (layout vs layout-land) works.

So my question is two-fold:

  1. Why wasn't it like this with Activities from the beginning.
  2. Why isn't this the default? Is there ever any reason why you would actually want your Fragment to be pointlessly destroyed and recreated on rotation? Because I can't think of any.

Edit

To clarify how I would do it:

class MyFragment extends Fragment
{
    // All the data.
    String mDataToDisplay;
    // etc.

    // All the views.
    TextView mViewToDisplayItIn;
    // etc.

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        mDataToDisplay = readFromSomeFileOrWhatever(); // Ignoring threading issues for now.
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.my_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        // At this point if mViewToDisplayItIn was not null, the old one will be GC'd.
        mViewToDisplayItIn = view.findViewById(R.id.the_text_view);
        mViewToDisplayItIn.setText(mDataToDisplay);
    }

    // Optionally:
    @Override
    public void onDestroyView()
    {
        // All the view (and activity) to be GC'd.
        mViewToDisplayItIn = null;
    }
}
like image 688
Timmmm Avatar asked Sep 19 '12 11:09

Timmmm


People also ask

What the fragment's method SetRetainInstance Boolean does?

SetRetainInstance(true) allows the fragment sort of survive. Its members will be retained during configuration change like rotation. But it still may be killed when the activity is killed in the background.

What are retained fragments?

A Fragment represents a reusable portion of your app's User Interface. Retained Fragment consists of the configuration change that causes the underlying Activity to be destroyed. The term "retained" refers to the fragment that will not be destroyed on configuration changes.


1 Answers

so that on configuration changes (which basically means device rotation)

And changing locale, changing SIMs, changing default font size, plugging in or removing an external keyboard, putting the device in a dock or removing it from same, etc.

you don't have to deal with onRetainNonConfigurationState()

That's onRetainNonConfigurationInstance().

bundle up all your data so it can be passed to the new Fragment (or Activity) instance only to be unbundled again

Your data should already be "bundled" (e.g., instance of private static inner class) and therefore it would not need to be "bundled" or "unbundled". Also, it frequently should not be "all your data", unless you are a fan of memory leaks.

And I assume (not tested) that resource resolution (layout vs layout-land) works.

Correct.

Is there ever any reason why you would actually want your Fragment to be pointlessly destroyed and recreated on rotation?

Sure.

As you note, all widgets are recreated, so data members tied to widgets are not only unnecessary to retain. Unless you specifically reset those to null on a retained fragment somehow, until onCreateView() is called again, those data members would hold onto the old widgets, which would hold onto the old activity instance, which would prevent that old activity instance from being garbage collected. AFAIK, onCreateView() is not going to be called until the fragment is going to be redisplayed, which may not be for quite some time (the fragment is not used in the new orientation, or the fragment is for some page in a ViewPager that the user visited in the old orientation but does not revisit in the new orientation, etc.). That means that the retained fragment may keep the old activity object around for a substantial period of time. Depending on what else that activity may have held onto (e.g., large Bitmap objects), that could be bad.

Similarly, a fragment that itself holds onto large data, where that fragment may or may not be used after the configuration change, is one that should not be retained.

Also, there will be fragments that simply have nothing needing to be retained (e.g., all data is populated by Loaders, which are already aware of configuration changes and handle them appropriately).

And so on.

A default of fragments being not retained is the safest course of action, with respect to garbage collection issues. You can opt into having some fragments be retained, but then the onus is on you to make sure that you are not screwing yourself by doing that.

like image 103
CommonsWare Avatar answered Oct 08 '22 18:10

CommonsWare