On fragments and life-cycles

“When should we trigger dependency injection in fragments?” Was a question my colleague asked me. “Well why not just in Fragment#onCreate()?”, I replied. However, my colleague pointed out that apparantly Fragment#onCreate() is not always called at the same time with relation to Activity#onCreate(). This is important for us as we initialize a Dagger component in the Activity and use that in the Fragment. We could of course just blindly follow the advise on the internet and solve this problem by doing whatever we want to do in Fragment#onActivityCreated(), but I wanted to know why the life-cycle methods where not always called in the same order.

For our purposes let’s consider the following piece of code:

public class MyActivity extends FragmentActivity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_layout);
    
    if (savedInstanceState == null) {
      Fragment f = new MyFragment();
      FragmentTransaction t = getFragmentManager().beginTransaction();
      t.add(R.id.fragment_container, f);
      t.commit();
    }
  }
}

We will analyze what happens when we first launch MyActivity and what happens later when it gets re-created. We will look at when the life-cycle methods are called and why they are called at that specific time. Hopefully this will give you a better understanding of the inner workings of fragments.

First launch

When we launch our activity for the first time, our UI will not contain any fragments yet, so we will add them ourselves. We achieve this by checking if savedInstanceState == null, if true this is a fresh launch of MyActivity.

We start by constructing a new instance of MyFragment. This will not call any life-cycle methods on the fragment, it only calls the normal constructor. On the next line we begin our FragmentTransaction. As this does not touch our fragment no life-cycle methods are called. We follow by adding the fragment instance to the transaction. This specifies that the fragment should be added to the FragmentManager and to the UI as a child of R.id.fragment_container, but also does not call any life-cycle methods on the fragment. Finally, we commit the transaction, which again will also not call any life-cycle methods on your fragment… Wait, what??? If none of those lines trigger the life-cycle methods, then when do they get called?

FragmentTransaction#commit();

If you look at the documentation of FragmentTransaction#commit() we already get a hint of what is going on. It reads “Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.”

First let’s have a look at what FragmentTransaction#commit() actually does. As FragmentTransaction is abstract we will be looking at BackStackRecord#commit(), which is the actual implementation used in the support lib:

final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, Runnable {

    // ...

    public int commit() {
        return commitInternal(false);
    }

    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }
    
    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

    // ...
}

The crucial line here is mManager.enqueueAction(this, allowStateLoss);. mManager is the FragmentManager, which in turn will call mActivity.mHandler.post() with this BackStackRecord instance (as it implements Runnable). So, everything in BackStackRecord#run() will be executed during the next frame, not the current one. You can read for yourself what BackStackRecord#run() actually does here, but in a nutshell it adds/removes/replaces/etc the fragment to the FragmentManager and then calls FragmentManagerImpl#moveToState(Fragment, int, int, int, boolean) which is where the actual life-cycle methods are called from your fragment.

Although you call commit in MyActivity#onCreate(Bundle), you can see that the fragment’s life-cycle methods are not actually called until the next UI frame iteration. At that point all your activity life-cycle methods (onCreate, onStart and onResume) have already completed.

Instance re-creation

We have seen in which order the life-cycle methods of a Fragment and Activity are called if the Fragment is added to the UI in MyActivity#onCreate(Bundle) by means of a FragmentTransaction, but what happens to the order if MyActivity gets destroyed and re-created, for instance when we rotate the screen? As hinted by all the questions on StackOverflow, the calling of life-cycle methods during recreation happens at a different point in time than when the fragment is initially added. To be specific, it happens during FragmentActivity#onCreate(Bundle) which we call in the first line of our MyActivity#onCreate(Bundle) (super.onCreate(savedInstanceState);).

Let’s have a look at what is going on here:

public class FragmentActivity extends Activity {

    // ...

    /**
     * Perform initialization of all fragments and loaders.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mFragments.attachActivity(this, mContainer, null);
        // Old versions of the platform didn't do this!
        if (getLayoutInflater().getFactory() == null) {
            getLayoutInflater().setFactory(this);
        }
        
        super.onCreate(savedInstanceState);
        
        NonConfigurationInstances nc = (NonConfigurationInstances)
                getLastNonConfigurationInstance();
        if (nc != null) {
            mAllLoaderManagers = nc.loaders;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
        mFragments.dispatchCreate();
    }

    // ...
}

If the savedInstanceState != null, then FragmentManagerImpl#restoreAllState(Parcelable, NonConfigurationInstances) is called, is where all the fragment instances are re-created. On the last line FragmentManagerImpl#dispatchCreate(); is called. This last line will actually call Fragment#onCreate(Bundle) on your fragment instance, which means that Fragment#onCreate(Bundle) will be called during your call to FragmentActivity#onCreate(Bundle) in your activity. (Note that all life-cycle methods of your fragments will now be called during their counterparts of FragmentActivity.)

As explained above the life-cycle methods of your Fragment are actually invoked during their counterparts of the Activity, which means that if your Fragment depends on certain objects created or set during Activity#onCreate(Bundle), you will need to create or set these objects before your call to super.onCreate(savedInstanceState) or (as suggested in the above SO link) move the logic in the fragment to a different life-cycle method (like Fragment#onActivityCreated(), which is guaranteed to be called after Activity#onCreate().

Bonus: Fragments in layouts?

What about if you add your Fragment in your layout instead? When will the life-cycle methods of your Fragment then be called with respect to those of the Activity?

Unsurprisingly the creation of the fragments in layouts is kicked off by FragmentActivity#setContentView(int) as the fragments are declared in your layout file. The creation of the fragments is done by FragmentActivity and happens because FragmentActivity intercepts the fragment tags by overriding the Factory2 implementation of Activity.

Let’s see what happens in FragmentActivity implementation of Factory2:

public class FragmentActivity extends Activity {
    
    // ...

    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return super.onCreateView(name, context, attrs);
        }

        final View v = mFragments.onCreateView(name, context, attrs);
        if (v == null) {
            return super.onCreateView(name, context, attrs);
        }
        return v;
    }

    // ...
}

The above shows that if the tag is a fragment tag, the FragmentActivity will delegate the creation of the view to the FragmentManagerImpl. In FragmentManagerImpl#onCreateView(View, String, Context, AttributeSet) the fragment class is instantiated and the fragment is moved to the Fragment.CREATED state. With fragments in layouts, this state also creates the view.

Wrap up

As you can see, using fragments in layouts will make sure the initial calls to the fragments life-cycle methods will be in the same order with relation to the activity life-cycle methods when the fragment is recreated during activity recreation. If your activity just has one (or more) static fragments that you don’t replace or remove, then it might make sense to add these fragments in a layout file instead of a fragment transaction as it will simplify dealing with activity recreation.

If you are dynamically adding, removing or replacing fragments, then be aware of the order of the life-cycle methods as your fragments life-cycle methods will be called before the activity counterparts have fully completed. For our purposes we chose to intialize our Dagger component before the call to super.onCreate(Bundle) in our Activity.

Please note that if you run into trouble with this, it might indicate that you have a dependency to the activity which creates coupling with your activity. You might want to consider if a fragment is the best solution here and if so whether it is possible to remove the coupling with your activity. After all, the purpose of fragments is to have re-usable pieces of UI, but if the fragments have strong dependencies on the order of things happening in the activity, they might not turn out to be that re-usable at all.