Rotation and Object Continuity

Now you are a good citizen, which is nice. Unfortunately, your app no longer handles rotation correctly. Try playing the 69_ohm-loko sound and rotating the screen: The sound will stop abruptly. (If it does not, make sure you have built and run the app with your recent onDestroy() implementation.)

Here is the problem: On rotation, the BeatBoxActivity is destroyed. As this happens, the FragmentManager destroys your BeatBoxFragment, too. In doing that, it calls BeatBoxFragment’s waning lifecycle methods: onPause(), onStop(), and onDestroy(). In BeatBoxFragment.onDestroy(), you call BeatBox.release(), which releases the SoundPool and stops sound playback.

You have seen how Activity and Fragment instances die on rotation before, and you have solved these issues using onSaveInstanceState(Bundle). However, that solution will not work this time, because it relies on saving data out and restoring it using Parcelable data inside a Bundle.

Parcelable, like Serializable, is an API for saving an object out to a stream of bytes. Objects may elect to implement the Parcelable interface if they are what we will call stashable here. Objects are stashed in Java by putting them in a Bundle, by marking them Serializable so that they can be serialized, or by implementing the Parcelable interface. Whichever way you do it, the same idea applies: You should not be using any of these tools unless your object is stashable.

To illustrate what we mean by stashable, imagine watching a television program with a friend. You could write down the channel you are watching, the volume level, and even the TV you are watching the program on. Once you do that, even if a fire alarm goes off and the power goes out, you can look at what you wrote down and get back to watching TV just like you were before.

So the configuration of your TV watching time is stashable. The time you spend watching TV is not, though: Once the fire alarm goes off and the power goes out, that session is gone. You can return and create a new session just like it, but you will experience an interruption no matter what you do. So the session is not stashable.

Some parts of BeatBox are stashable – everything contained in Sound, for example. But SoundPool is more like your TV watching session. Yes, you can create a new SoundPool that has all the same sounds as an older one. You can even start playing again right where you left off. But you will always experience a brief interruption, no matter what you do. That means that SoundPool is not stashable.

Non-stashability tends to be contagious. If a non-stashable object is critical to another object’s mission, that other object is probably not stashable, either. Here, BeatBox has the same mission as SoundPool: to play back sounds. Therefore, ipso facto, Q.E.D.: BeatBox is not stashable. (Sorry.)

The regular savedInstanceState mechanism preserves stashable data for you, but BeatBox is not stashable. You need your BeatBox instance to be continuously available as your Activity is created and destroyed.

What to do?

Retaining a fragment

Fortunately, fragments have a feature that you can use to keep the BeatBox instance alive across a configuration change: retainInstance. Within your override of BeatBoxFragment.onCreate(…), set a property on the fragment.

Listing 21.18  Calling setRetainInstance(true) (BeatBoxFragment.java)

    public static BeatBoxFragment newInstance() {
        return new BeatBoxFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

        mBeatBox = new BeatBox(getActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

By default, the retainInstance property of a fragment is false. This means it is not retained, but is destroyed and re-created on rotation along with the activity that hosts it. Calling setRetainInstance(true) retains the fragment. When a fragment is retained, the fragment is not destroyed with the activity. Instead, it is preserved and passed along intact to the new activity.

When you retain a fragment, you can count on all of its instance variables (like mBeatBox) to keep the same values. When you reach for them, they are simply there.

Run BeatBox again. Play the 69_ohm-loko sound, rotate the device, and confirm that playback continues unimpeded.

Rotation and retained fragments

Let’s take a closer look at how retained fragments work. Retained fragments take advantage of the fact that a fragment’s view can be destroyed and re-created without having to destroy the fragment itself.

During a configuration change, the FragmentManager first destroys the views of the fragments in its list. Fragment views always get destroyed and re-created on a configuration change for the same reasons that activity views are destroyed and re-created: If you have a new configuration, then you might need new resources. Just in case better matching resources are now available, you rebuild the view from scratch.

Next, the FragmentManager checks the retainInstance property of each fragment. If it is false, which it is by default, then the FragmentManager destroys the fragment instance. The fragment and its view will be re-created by the new FragmentManager of the new activity on the other side (Figure 21.9).

Figure 21.9  Default rotation with a UI fragment

Figure shows Rotation with a default UI fragment.

On the other hand, if retainInstance is true, then the fragment’s view is destroyed but the fragment itself is not. When the new activity is created, the new FragmentManager finds the retained fragment and re-creates its view (Figure 21.10).

Figure 21.10  Rotation with a retained UI fragment

Figure shows Rotation with a retained UI fragment.

A retained fragment is not destroyed, but it is detached from the dying activity. This puts the fragment in a retained state. The fragment still exists, but it is not hosted by any activity (Figure 21.11).

Figure 21.11  Fragment lifecycle

Figure shows Fragment Lifecycle.

The retained state is only entered into when two conditions are met:

  • setRetainInstance(true) has been called on the fragment

  • the hosting activity is being destroyed for a configuration change (typically rotation)

A fragment is only in the retained state for an extremely brief interval – the time between being detached from the old activity and being reattached to the new activity that is immediately created.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.90.141