Android fragment created twice on orientation change

androidandroid-orientationfragment

i am having this weird problem where my list fragment is created twice, once when the super.oncreate is called on the parent activity and once when the setContentView is called on the same parent activity. It is a simple application where i use different layout for portrait and landscape orientation.

Here is the main activity:

private HeadlinesFragment headlines;

@Override
public void onCreate(Bundle savedInstanceState) {
    Log.w("MainActivity", "Before super.onCreate: " + this.toString());
    super.onCreate(savedInstanceState);
    Log.w("MainActivity", "Before setContentView: " + this.toString());
    setContentView(R.layout.news_articles);

    //check to see if its portrait
    if (findViewById(R.id.fragment_container) != null) {
        if(getSupportFragmentManager().findFragmentById(R.id.fragment_container) == null) {
            headlines = new HeadlinesFragment();
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, headlines).commit();
        }
    }
}

here is the news_articles in layout-land folder:

<LinearLayout 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.android.fragments.HeadlinesFragment"
          android:id="@+id/headlines_fragment"
          android:layout_weight="1"
          android:layout_width="0dp"
          android:layout_height="match_parent" />

<fragment android:name="com.example.android.fragments.ArticleFragment"
          android:id="@+id/article_fragment"
          android:layout_weight="2"
          android:layout_width="0dp"
          android:layout_height="match_parent" />

here is the news_articles in layout folder (for the portrait orientation)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

here is the headlinesfragment thats been created twice

public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;

// The container Activity must implement this interface so the frag can deliver messages
public interface OnHeadlineSelectedListener {
    /** Called by HeadlinesFragment when a list item is selected */
    public void onArticleSelected(int position);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.w("HeadlinesFragment", "inside onCreate: " + this.toString());

    // We need to use a different list item layout for devices older than Honeycomb
    int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            android.R.layout.simple_list_item_activated_1 : android.R.layout.simple_list_item_1;

    // Create an array adapter for the list view, using the Ipsum headlines array
    setListAdapter(new ArrayAdapter<String>(getActivity(), layout, Ipsum.Headlines));
}

@Override
public void onStart() {
    super.onStart();

    // When in landscape layout, set the listview to highlight the selected list item
    // (We do this during onStart because at the point the listview is available.)
    if (getFragmentManager().findFragmentById(R.id.article_fragment) != null) {
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    }
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception.
    try {
        mCallback = (OnHeadlineSelectedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnHeadlineSelectedListener");
    }
}


@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    // TODO Auto-generated method stub
    super.onCreateOptionsMenu(menu, inflater);
}

@Override
public void onDestroy() {
    Log.w("HeadlinesFragment", "inside onDestroy: " + this.toString());
    super.onDestroy();
}
}

here is the articlefragment

public class ArticleFragment extends Fragment {
final static String ARG_POSITION = "position";
int mCurrentPosition = 0;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
    Bundle savedInstanceState) {
    Log.w("ArticleFragment", "inside onCreateView: " + this.toString());

    if (savedInstanceState != null) {
        mCurrentPosition = savedInstanceState.getInt(ARG_POSITION);
    }

    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.article_view, container, false);
    return view;
}

@Override
public void onStart() {
    super.onStart();
    Bundle args = getArguments();
    if (args != null) {
        // Set article based on argument passed in
        updateArticleView(args.getInt(ARG_POSITION));
    } else if (mCurrentPosition != -1) {
        // Set article based on saved instance state defined during onCreateView
        updateArticleView(mCurrentPosition);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Save the current article selection in case we need to recreate the fragment
    outState.putInt(ARG_POSITION, mCurrentPosition);
}

@Override
public void onDestroy() {
    Log.w("ArticleFragment", "inside onDestroy: " + this.toString());
    super.onDestroy();
}

}

The problem in detail is this:

1) start the application in portrait orientation
2) the setContentView is called and news_articles is loaded but its the one with the fragment_container.
3) headlinesfragment is created // so far normal behaviour
4) change orientation to landscape
5) mainActivity is destroyed -> headlinefragment is destroyed
6) super.oncreate on mainactivity is called
7) Headlinefragment is created
8) setcontentview on mainactivity is called
9) another headlinefragment is created //problem

i have placed the logs as can be seen in the code above and here is the output when i start the app in portrait mode and i change to landscape.

W/MainActivity(6925): Before super.onCreate: MainActivity@41d81238
W/MainActivity(6925): Before setContentView: MainActivity@41d81238
W/HeadlinesFragment(6925): inside onCreate: HeadlinesFragment{41d8d4d8 #0 id=0x7f050001}
W/MainActivity(6925): inside onDestroy: MainActivity@41d81238
W/HeadlinesFragment(6925): inside onDestroy: HeadlinesFragment{41d8d4d8 # 0id=0x7f050001}
W/MainActivity(6925): Before super.onCreate: MainActivity@41ea6258
W/HeadlinesFragment(6925): inside onCreate: HeadlinesFragment{41ea7290 #0 id=0x7f050001}
W/MainActivity(6925): Before setContentView: MainActivity@41ea6258
W/HeadlinesFragment(6925): inside onCreate: HeadlinesFragment{41eb1f30 #1 id=0x7f050002}
W/ArticleFragment(6925): inside onCreateView: ArticleFragment{41eb5f20 #2 id=0x7f050003}

I hope i have been clear with my code and logs, it seems to me super.oncreate and setcontentview both create a a headlinesfragment each; at least i think.

my question is why 2 headlinesfragment instances are created and how i can avoid such a situation.

many thanks for any help regarding this

Best Answer

In the onCreate of your Activity, you can check the state of your savedInstanceState bundle. If it isn't null, it means a configuration change occurred (in your case, an screen orientation change) and that you don't need to recreate your Fragment.

Another mistake that you were doing was that you were trying to retrieve your Fragment with the findFragmentById. Instead of passing the Fragment id, you're giving it the id of the View attached to the Fragment, which is different (and that's the reason why I'm guessing this was always returning null).

A correct implementation would be more like this (this is your Activity) :

    //check to see if its portrait
    if (findViewById(R.id.fragment_container) != null) {
        if(savedInstanceState == null) {
            headlines = new HeadlinesFragment();
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, headlines, FRAGMENT_TAG_STRING).commit(); // Use tags, it's simpler to deal with
        } else {
            headlines = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_STRING);
        } 
   }