The onCreateView and onActivityCreated snippet is called twice

I am developing an application using Android 4.0 ICS and snippets.

Consider this modified example from the sample application example API ICS 4.0.3 (API level 15):

public class FragmentTabs extends Activity { private static final String TAG = FragmentTabs.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); bar.addTab(bar.newTab() .setText("Simple") .setTabListener(new TabListener<SimpleFragment>( this, "mysimple", SimpleFragment.class))); if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); } public static class TabListener<T extends Fragment> implements ActionBar.TabListener { private final Activity mActivity; private final String mTag; private final Class<T> mClass; private final Bundle mArgs; private Fragment mFragment; public TabListener(Activity activity, String tag, Class<T> clz) { this(activity, tag, clz, null); } public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) { mActivity = activity; mTag = tag; mClass = clz; mArgs = args; // Check to see if we already have a fragment for this tab, probably // from a previously saved state. If so, deactivate it, because our // initial state is that a tab isn't shown. mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { Log.d(TAG, "onTabUnselected detaching fragment " + mTag); ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); } } public static class SimpleFragment extends Fragment { TextView textView; int mNum; /** * When creating, retrieve this instance number from its arguments. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state")); if(savedInstanceState != null) { mNum = savedInstanceState.getInt("number"); } else { mNum = 25; } } @Override public void onActivityCreated(Bundle savedInstanceState) { Log.d(TAG, "onActivityCreated"); if(savedInstanceState != null) { Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number")); } super.onActivityCreated(savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { Log.d(TAG, "onSaveInstanceState saving: " + mNum); outState.putInt("number", mNum); super.onSaveInstanceState(outState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state")); textView = new TextView(getActivity()); textView.setText("Hello world: " + mNum); textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); return textView; } } 

}

Here is the result obtained by running this example, and then turning the phone:

 06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple 06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state 06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25 06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25 06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple 06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 

My question is why calling onCreateView and onActivityCreated twice? First time with Stateful Bundle and second time with null savedInstanceState?

This causes problems with maintaining the state of the fragment during rotation.

+81
android android-fragments
Jun 11 '12 at 15:44
source share
5 answers

Well, thatโ€™s what I found out.

I did not understand that all fragments associated with the activity when changing the configuration (turning the phone) are recreated and added to the activity. (which makes sense)

What happens in the TabListener constructor, the tab was detached if it was found and tied to activity. See below:

 mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } 

Later in the onCreate action, the previously selected tab was selected from the state of the saved instance. See below:

 if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } 

When a tab has been selected, it will be reconnected in the onTabSelected callback.

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } 

The attached snippet is the second call to the onCreateView and onActivityCreated methods. (The first is when the system recreates activity and all attached fragments). The first time the onSavedInstanceState Bundle package saved data, but not the second time.

The solution is to not separate the fragment in the TabListener constructor, just leave it attached. (You still need to find it in the FragmentManager tag by tag). Also in the onTabSelected method, I check if the fragment is detached before attaching it. Something like that:

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { if(mFragment.isDetached()) { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } else { Log.d(TAG, "onTabSelected fragment already attached " + mTag); } } } 
+22
Jun 12 '12 at 12:27
source share

I also scratched my head a bit about this, and since Dave's explanation is a little hard to understand, I will post my (apparently working) code:

 private class TabListener<T extends Fragment> implements ActionBar.TabListener { private Fragment mFragment; private Activity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag); } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.replace(android.R.id.content, mFragment, mTag); } else { if (mFragment.isDetached()) { ft.attach(mFragment); } } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { } } 

As you can see, this is similar to an Android sample, except that it does not detach in the constructor, and instead replace instead .

After repeated input and a trial error, I found that searching for a fragment in the constructor seems to make the problem with double onCreateView magic escape (I assume it just ends up null for onTabSelected when called through ActionBar.setSelectedNavigationItem () when saving / restoring state).

+43
Oct 07
source share

The two answers above show solutions for Activity with the navigation mode NAVIGATION_MODE_TABS , but I had the same problem with NAVIGATION_MODE_LIST . This made my fragments inexplicably lose their state when the screen orientation changed, which was really annoying. Fortunately, thanks to their useful code, I managed to figure it out.

Basically, when using list navigation, `` onNavigationItemSelected () is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's onCreateView () from being called twice, this initial automatic call to onNavigationItemSelected () should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes onCreateView () `will be called twice!

See my implementation of onNavigationItemSelected() below.

 public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener { private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; private boolean mIsUserInitiatedNavItemSelection; // ... constructor code, etc. @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) { getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM)); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex()); super.onSaveInstanceState(outState); } @Override public boolean onNavigationItemSelected(int position, long id) { Fragment fragment; switch (position) { // ... choose and construct fragment here } // is this the automatic (non-user initiated) call to onNavigationItemSelected() // that occurs when the activity is created/re-created? if (!mIsUserInitiatedNavItemSelection) { // all subsequent calls to onNavigationItemSelected() won't be automatic mIsUserInitiatedNavItemSelection = true; // has the same fragment already replaced the container and assumed its id? Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container); if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass())) { return true; //nothing to do, because the fragment is already there } } getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit(); return true; } } 

I borrowed inspiration for this solution from here .

+11
Jan 12 '13 at 16:44
source share

I had the same problem with a simple Activity carrying only one fragment (which was sometimes replaced). Then I realized that I use onSaveInstanceState only in the fragment (and onCreateView to check for savedInstanceState), and not in activity.

An action is activated on the device containing fragments that are restarted, and onCreated is called. There I attached the necessary fragment (which is correct on the first start).

On the Android device, first re-created the fragment that was visible, and then called onCreate from the containing action in which my fragment was attached, thus replacing the original visible one.

To avoid this, I simply changed my activity to check on savedInstanceState:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); If (savedInstanceState != null) return; // following code to attach fragment initially 

I did not even overwrite onSaveInstanceState activity.

+8
Sep 30 '13 at 17:52
source share

I like it because every time you create your TabListener ... so the system recreates your fragment from the savedInstanceState file, and then you do it again in your onCreate.

You have to wrap this in if(savedInstanceState == null) , so it only starts if there is no saved if(savedInstanceState == null) .

+3
Jun 11 2018-12-12T00:
source share



All Articles