13

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

Pishtewan Agha
  • 556
  • 1
  • 6
  • 12

2 Answers2

17

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);
        } 
   }
OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
ben
  • 1,151
  • 10
  • 20
  • Thanks for your response however the problem is when the application turns to landscape orientation so the code you have given isnt executed anyway. Also regarding the code you have given, when the application firsts starts in portrait mode, the savedInstanceState is null and the else block is executed so how can we find a HeadlinesFragment by tag if we never got to create it in the first place ? – Pishtewan Agha May 12 '13 at 16:03
  • 4
    I think the if statement condition is backwards here. If there is a saved instance state, you should find the existing fragment. If the saved instance state is null, you should create a new fragment. – Razz Apr 09 '15 at 15:24
  • @Razz is correct. NOTE: if savedInstanceState IS null then you create a new fragment, otherwise the fragment already exists! So, just change the inner if statement to if(savedInstanceState == null) – Lucas Crawford Nov 08 '15 at 00:07
2

Override onSavedInstanceState without calling it's super.

Siamaster
  • 941
  • 12
  • 19
  • 5
    Can you expand on your answer, explain your reasoning and there are also pitfalls with not calling the super in this case as well. – CurlyPaul May 22 '14 at 10:18