Handle Fragment duplication on Screen Rotate (with sample code)
android, android-fragmentactivity, android-fragments, android-framelayout, android-listfragment
Solution
I finally come up with a solution to prevent unwanted Fragment re-creating upon the Activity re-creates. It is like what i mentioned in the question:
The only neat way i found is doing `getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();` in `onSaveInstanceState()`. But it will raise another problems...
... with some improvements.
In `onSaveInstanceState()`:
@Override
protected void onSaveInstanceState(Bundle outState) {
if (isPortrait2Landscape()) {
remove_fragments();
}
super.onSaveInstanceState(outState);
}
private boolean isPortrait2Landscape() {
return isDevicePortrait() && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
}
and the `isDevicePortrait()` would be like:
private boolean isDevicePortrait() {
return (findViewById(R.id.A_View_Only_In_Portrait) != null);
}
*Notice that we cannot use `getResources().getConfiguration().orientation` to determine if the device is currently literally Portrait. It is because the `Resources` object is changed RIGHT AFTER the screen rotates - EVEN BEFORE `onSaveInstanceState()` is called!!
If we do not want to use `findViewById()` to test orientation (for any reasons, and it's not so neat afterall), keep a global variable `private int current_orientation;` and initialise it by `current_orientation = getResources().getConfiguration().orientation;` in `onCreate()`. This seems neater. But we should be aware not to change it anywhere during the Activity lifecycle.
*Be sure we `remove_fragments()` before `super.onSaveInstanceState()`.
(Because in my case, i remove the Fragments from the Layout, and from the Activity. If it is after `super.onSaveInstanceState()`, the Layout will already be saved into the Bundle. Then the Fragments will also be re-created after the Activity re-creates. ###)
### I have proved this phenomenon. But the reason of What to determine a Fragment restore upon Activity re-create? is just by my guess. If you have any ideas about it, please answer my another question.
Problem
There are some similar answers, but not to this situation. My situation is simple. I have an Activity with two different layouts, one in Portrait, another in Landscape. In Portrait, i use `<FrameLayout>` and add `Fragment` into it dynamically. In Landscape, i use `<fragment>` so the `Fragment` is static. (in fact this doesn't matter) It first starts in Portrait, then i added the `Fragment` by simply: ``` ListFrag listFrag = new ListFrag(); getSupportFragmentManager().beginTransaction() .replace(R.id.FragmentContainer, listFrag).commit(); ``` where `ListFrag extends ListFragment`. Then i do a screen rotate. I found the listFrag is re-creating in the Landscape mode. (In which i noticed the `onCreate()` method is called again with a non-null Bundle) i tried to use `setRetainInstance(false)` like @NPike said in this post. But the `getRetainInstance()` is already `false` by default. It does not do what i expected as the docs said. Could anyone please explain? The fragment i am dealing with, is a `ListFragment`, which does `setListAdapter()` in `onCreate()`. So the `if (container == null) return null;` method cannot be used here. (or i dont know how to apply). I got some hints from this post. Should i use `if (bundle != null) setListAdapter(null); else setListAdapter(new ...);` in my `ListFragment`? But is there a nicer way to indeed removing/deleting the fragment when it is destroyed/detached, rather than doing it in its creation time? (so as the `if (container == null) return null;` method) Edit: The only neat way i found is doing `getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();` in `onSaveInstanceState()`. But it will raise another problems. When screen is partially obscured, like WhatsApp or TXT dialogue boxes pop up, the fragment will be disappeared also. (this is relatively minor, just visual issue) When screen rotate, the Activity is completely destroyed and re-created. So i can re-add the fragment in `onCreate(Bundle)` or `onRestoreInstanceState(Bundle)`. But in the case of (1), as well as switching Activities, neither `onCreate(Bundle)` nor `onRestoreInstanceState(Bundle)` will be called when user get back to my Activity. I have nowhere to recreate the Activity (and retrieve data from Bundle). Sorry I didn't say it clearly that, i already have a decision making, which the `getSupportFragmentManager()...replace(...).commit();` line only run in Portrait mode. Example code I have extracted the simple code, for better illustration of the situration :) MainActivity.java ``` package com.example.fragremovetrial; import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (findViewById(R.id.FragmentContainer) != null) { System.out.println("-- Portrait --"); // Portrait ListFrag listFrag = new ListFrag(); getSupportFragmentManager().beginTransaction() .replace(R.id.FragmentContainer, listFrag).commit(); } else { System.out.println("-- Landscape --"); // Landscape } } @Override protected void onResume() { super.onResume(); if (findViewById(R.id.FragmentContainer) != null) { System.out.println("getRetainInstance = " + getSupportFragmentManager().findFragmentById(R.id.FragmentContainer).getRetainInstance()); } } } ``` layout/activity_main.xml ``` <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/FragmentContainer" android:layout_width="fill_parent" android:layout_height="fill_parent" /> ``` layout-land/activity_main.xml (doesn't matter) ``` <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > </LinearLayout> ``` ListFrag.java ``` package com.example.fragremovetrial; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.widget.ArrayAdapter; public class ListFrag extends ListFragment { private String[] MenuItems = { "Content A", "Contnet B" }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.out.println("ListFrag.onCreate(): " + (savedInstanceState == null ? null : savedInstanceState)); setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, MenuItems)); } } ``` Notice i got the following Debug messages -- Portrait -- ListFrag.onCreate(): null getRetainInstance = false (rotate Port -> Land) ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4052dd28}] -- Landscape -- Previously focused view reported id 16908298 during save, but can't be found during restore. (rotate Land -> Port) ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@405166c8}] -- Portrait -- ListFrag.onCreate(): null getRetainInstance = false (rotate Port -> Land) ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@4050fb40}] -- Landscape -- Previously focused view reported id 16908298 during save, but can't be found during restore. (rotate Land -> Port) ListFrag.onCreate(): Bundle[{android:view_state=android.util.SparseArray@40528c60}] -- Portrait -- ListFrag.onCreate(): null getRetainInstance = false where the number of Fragment created will not increase infinitely as screen keep rotating, which i cannot explain. (please help)