Detail fragment re-starts even when rotated from landscape to vertical. (Master-Detail Flow)

Viewed 862

In an Activity, lets call it MasterActivity, I want to load the detail fragment with media playback only in landscape mode. The media automatically starts when ready.

Master-Detail Flow setup: I have two xmls, activity_master and activity_master.xml(land). The container view with id "detail_container" is only in the landscape xml. The purpose of the landscape xml with a detail container is to show master-detail both on the same screen, on width >900.

In onCreate(), this is how I'm determining the screen orientation though checking the existence of the "detail_container", like so:

if (findViewById(R.id.detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
    mTwoPane = true;
}

and also in OnCreate() of the activity, I have the following code to automatically load the fragment when activity is in two pane mode (landscape on large-screen layouts)

if (mTwoPane) {
    getSupportFragmentManager().beginTransaction()
    .replace(R.id.detail_container,
             someFragment.newInstance(MEDIA_URI))
    .commit();
}

Problem:

When I start the activity in vertical, the fragment isn't loaded (expected).

When I start the activity in landscape, the fragment is loaded (expected).

On screen rotation, fragment is destroyed (expected, playback stops and resources released only in onDestory());

The problem is when I start activity in landscape but rotate it to vertical, the fragment restarts, and media playback start again (unexpected).

My Goal: I want the fragment to automatically load when the device is in landscape on large screen devices, on device rotation the fragment shouldn't load again.

EDIT: In vertical mode, the fragment shouldn't load automatically, user would click in master activity, opens the detail activity, and the detail activity would host the detail fragment.

How should I go about this? Thank You in Advance

Extra Info: Test physical device is a 7' tablet running Android 5.0 API 21, the issue is also present in emulator.

3 Answers

When a config change occurs, Android will make sure all fragments that are attached to an activity are recreated and reattached back to the new activity's FragmentManager ; this is separate of whether or not the fragments took setRetainInstance(...) into consideration.

So it looks like what you want to do is go against the automatic reattaching of fragments. I was curious about this too so I took a little deep dive into FragmentActivity.onCreate(...) and FragmentManager but I couldn't find anything exposed that allows developers to disallow this automatic process. You can, however, work around the process with a FragmentTransaction by performing the following in your code:

FragmentManager manager = getSupportFragmentManager();

if (mTwoPane) {
  // set up your two pane
  manager.beginTransaction()
    .replace(R.id.detail_container,
      someFragment.newInstance(MEDIA_URI),
      someFragment.TAG)
    .commit();
} else {
  // this is not two pane, so remove the fragment if it is attached
  Fragment detail = manager.findFragmentByTag(someFragment.TAG);
  if (detail != null) {
    manager.beginTransaction()
      .remove(detail)
      .commit();
  }
}

I don't know how your layout XML looks like but I suggest to define the Fragment in your "landscape" layout file only by creating a new directory called /res/layout-land if it not exists.

Layout may look similar to this:

<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    ...
    <fragment android:name="com.example.YourPlaybackFragment"
            android:id="@id/playback_fragment"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    ...
</SomeLayout>

Here's a useful link on the official Android doc.

Then the Fragment is only inflated in landscape mode and Android takes care of that. Same procedure can be done for bigger screens. That frees your MasterActivity from layout logic.

Your playback logic goes into the Fragment. If the Activity, for whatever reason, needs access to the Fragment then you pull it in with

YourPlaybackFragment fragment = (YourPlaybackFragment) getFragmentManager().findFragmentById(R.id.playback_fragment);

Hope that gives you the right direction.

Edit: In that case your best bet is to add the Fragment programmatically in the onCreate() method similar to this.

if (findViewById(R.id.detail_container) != null) {
   // detect the landscape
   Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
   if(display.getRotation() == Surface.ROTATION_90) {
      // add the fragment
      getSupportFragmentManager().beginTransaction().add/replace(...)
   }
}

Disclaimer: not tested.

Since you have two different activities, the fragment no longer exists in the same context so that might be a little difficult to produce the behavior you're looking for. Try to do the following:

Portrait : xml layout with only one container for the MasterFragment. Swap this container with the DetailFragment when selected.

Landscape : xml layout with two containers, one for MasterFragment and one for DetailFragment. If DetailFragment already exists (user was viewing it in portrait before the orientation change), the fragment manager knows about it. Find the existing fragment and attach it to the appropriate container.

Assuming you're letting your activity be recreated on orientation changes, if you're using setRetainInstance(true), the fragment manager can hold onto your existing fragment and should re-attach the same one automatically on orientation change. If you want it to reattach, you should check to make sure the fragment doesn't exist yet before calling replace(...). If you want to attach it somewhere else, you can grab the existing one and put it in a different container.

if (mTwoPane) {
        if (getSupportFragmentManager().findFragmentByTag(someFragment.TAG) != null) {
            // fragment already exists so either do nothing (auto reattach to R.id.detail_container)
            // or manually attach it to a different container
            return;
        }
        fragment = someFragment.newInstance(MEDIA_URI);
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.detail_container,
                         someFragment.newInstance(MEDIA_URI),
                         someFragment.TAG)
                .commit();
}
Related