Fragments were introduced into Android in 2010 by Dianne Hackborn. That’s what she wrote in the original commit message:

Author: Dianne Hackborn <hackbod@google.com>
Date: Thu Apr 15 14:45:25 2010 -0700
Introducing Fragment.
Basic implementation of an API for organizing a single activity into separate, discrete pieces. Currently supports adding and removing fragments, and performing basic lifecycle callbacks on them.

The idea of “organizing a single activity into separate, discrete pieces” was absolutely great. However, Fragments have had very troubled history. For a long time now, they have been among the most controversial Android’s APIs. Many professional Android developers won’t use Fragments in their projects under any circumstances. Not because they don’t understand their benefits, but because they clearly see Fragments’ drawbacks and limitations.

These developers do have a point because it’s simply impossible to overstate the complexity of Fragment lifecycle. Just take a look at this diagram. It’s intimidating.

In the recent years, Google decided to push the ecosystem in the direction of so-called single-Activity applications. This meant that Fragments once again gained a lot of focus and some love. Many old bugs and inconsistencies were fixed and some nice features were added.

In this post I will describe my approach to handling of Fragment’s lifecycle. The main goals of this technique is to be pragmatic and keep the complexity of Fragments in Android applications at minimum.

Activity Lifecycle

As you’ll see shortly, when I use Fragments, I try to decouple them from Activity lifecycle as much as possible. Basically, Fragments shouldn’t know anything about the Activity they’re hosted in.

However, Fragment lifecycle have many similarities to Activity lifecycle. In some sense, Fragments are light-weight Activities. I already wrote an article about Activity’s lifecycle, and, in this post, I’ll draw many analogies between them. However, I don’t want to repeat myself. Therefore, I’m going to assume that you already read that article about Activities. If you haven’t, please do it now before you proceed with this post.

My Take on Fragment Lifecycle

My approach to Fragment’s lifecycle aims to achieve two goals:

  1. Make Fragment’s lifecycle handling logic as similar as possible to Activity’s logic.
  2. Make Fragment’s lifecycle handling logic independent of Activity lifecycle.

Let’s discuss each of these goals in more details.

By handling Fragment’s lifecycle similar to Activity’s lifecycle I greatly reduce the overall complexity of the application. Developers will need to learn just one approach instead of two different ones. This means less effort during development, easier maintenance and quicker ramp-up of new team members. I’m also completely sure that doing so reduces risk of bugs, though that’s subjective.

Making Fragment independent of Activity’s lifecycle goes back to the diagram of their respective lifecycles. Each of them is extremely complex, but still manageable in isolation. However, thinking about both lifecycles and the inter-dependencies between them is beyond what I’m capable of.

By satisfying this two goals I greatly reduce the complexity associated with Fragments thus making them more attractive.

onCreate(Bundle)

Remember that you don’t have access to Activity’s constructor so you can’t use it to inject dependencies into Activity? Well, the good news is that Fragment does have a public constructor, and we can even define additional ones. The bad news is that doing so will lead to serious bugs, so we can’t really do it.

Android will destroy and then re-create Fragments during so called save & restore flow. The re-creation mechanism uses reflective calls to Fragment’s no-arguments constructor. So, if you used constructor with arguments to instantiate the Fragments in the first place and passed dependencies into it, all of these dependencies will be set to null after save & restore. Not cool.

Therefore, just like with Activities, you’ll use onCreate(Bundle) method as a replacement for constructors. Dependency injection and initialization of Fragment’s members takes place here.

However, there is one major difference form Activity’s onCreate(Bundle): you must not touch or do anything related to Android Views in Fragment’s onCreate(Bundle). This is very important. The reason for that will become clear in the next section.

All in all, Fragment’s onCreate(Bundle) will look similar to this:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getInjector().inject(this); // inject dependencies

        if (savedInstanceState != null) {
            mWelcomeDialogHasAlreadyBeenShown = savedInstanceState.getBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN);
        }
    }

Update: since I originally wrote this article, it became possible to instantiate Fragments using non-default constructors with arguments. However, to do that, you’ll need to use a new component called FragmentFactory. In my opinion, this additional complexity is justified only if you want to use dependency injection in multi-module projects. For more details, read this post about FragmentFactory.

onCreateView(LayoutInflater, ViewGroup, Bundle)

This method is unique to Fragment and it’s the most prominent difference between Activity and Fragment lifecycle.

I will get a bit ahead of myself and tell you that this method is also the “root of all evil” associated with Fragments. I will discuss this “evil” later, but just keep in mind that if you’re using Fragments, you better not underestimate the complexity of onCreateView(LayoutInflater, ViewGroup, Bundle).

So, what’s the story?

Activities operate under assumption of being associated with a single View hierarchy for the entire lifecycle. You initialize this hierarchy in Activity’s onCreate(Bundle) and it stays attached to it until garbage collected. You can manually change the composition of Activity’s View hierarchy, but Android will not do anything like that on your behalf.

Fragments, on the other hand, operate under assumption that Android should be capable of destroying and re-creating the View hierarchy associated with a specific Fragment instance. In other words, Fragments can have multiple View hierarchies during their lifecycle and it’s up to Android to decide when the replacement takes place.

Given that View hierarchy can be replaced at runtime, it should be clear now why you mustn’t touch it in Fragment’s onCreate(Bundle). This method will be called just once after the fragment is attached to the enclosing Activity, therefore it can’t support the dynamic nature of Fragment’s View hierarchy.

Enter onCreateView(LayoutInflater, ViewGroup, Bundle).

This method will be called by Android every time a new View hierarchy needs to be created. Your job is to create and initialize View hierarchy to the correct state, and then return it from this method. Android will take care of it from there.

The main rule for implementations of this method is: all Fragment’s members holding references to objects related to View hierarchy must be assigned inside onCreateView(LayoutInflater, ViewGroup, Bundle). In other words, if Fragment holds references to Views or related objects in its fields, make sure that all these fields are assigned in this method. This is really important.

All in all, the general form of onCreateView(LayoutInflater, ViewGroup, Bundle) should be something along these lines:

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.some_layout, container, false);
        mSomeView = rootView.findViewById(R.id.some_view); // must assign all Fragment's fields which are Views
        mLstSomeList = rootView.findViewById(R.id.lst_some_list); // must assign all Fragment's fields which are Views
        mAdapter = new SomeAdapter(getContext()); // must assign all Fragment's fields related to View hierarchy
        mLstSomeList.setAdapter(mAdapter);
        return rootView;
    }

Another interesting thing about this method is that it also receives Bundle with saved state. Honestly, I find it troubling. It’s like the developers weren’t sure where exactly the state should be restored, so they injected this Bundle into several methods for us to figure it out ourselves.

Don’t restore state in this method. I will explain why when we talk about onSaveInstanceState(Bundle).

Edit:

User Boza_s6 provided his (her?) feedback for this article on Reddit, and we had a very interesting discussion. The question was whether my method can introduce memory leaks if lists and adapters are used in Fragments. In light of this discussion, I would like to make sure this subject is clear.

There is no risk of memory leaks if you follow the rules I share in this article. In fact, part of the reasons I use this approach is to mitigate Fragment’s intrinsically higher risk of memory leaks.

The rule for onCreateView(LayoutInflater, ViewGroup, Bundle) is that every Fragment’s field related to View hierarchy must be assigned in this method. This includes list Adapters, user interaction listeners, etc. The only way to keep Fragment’s code maintainable and free of tricky corner cases is to ensure that this method re-creates the entire View hierarchy and all the associated objects from scratch.

onStart()

This method in Fragment has exactly the same responsibilities and guidelines as Activity’s onStart(). You can read about it my previous article about Activity’s lifecycle.

So, the general form of Fragment’s onStart() will be something like this:

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

        mSomeView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleOnSomeViewClick();
            }
        });

        mFirstDependency.registerListener(this);

        switch (mSecondDependency.getState()) {
            case SecondDependency.State.STATE_1:
                updateUiAccordingToState1();
                break;
            case SecondDependency.State.STATE_2:
                updateUiAccordingToState2();
                break;
            case SecondDependency.State.STATE_3:
                updateUiAccordingToState3();
                break;
        }

        if (mWelcomeDialogHasAlreadyBeenShown) {
            mFirstDependency.intiateAsyncFunctionalFlowAndNotify();
        } else {
            showWelcomeDialog();
            mWelcomeDialogHasAlreadyBeenShown = true;
        }
    }

As you see, this is the place to put most of Fragment’s functional logic. The fact that this approach is consistent between Activity and Fragment is of huge benefit.

Update: just like with Activities, you no longer need to register View listeners in onStart() and unregister in onStop() because the behavior that required that was fixed at the framework level and backported to API 19. Just like with Activities, I decided to keep this discussion for historical reasons and developers who still support APIs lower than 19.

 onResume()

Handling of this method is identical to Activity’s onResume().

onPause()

It’s identical to Activity’s onPause()as well.

onStop()

Again, this method is identical to Activity’s onStop(). It should follow the following pattern:

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

        mSomeView.setOnClickListener(null);

        mFirstDependency.unregisterListener(this);
    }

The interesting and surprising part here is unregistration of the click listener. I explained why you might want to do that in the post about Activity lifecycle, so I won’t repeat myself. However, I would like to use this opportunity to address the question I’ve been asked in context of the previous post: is this unregistration mandatory?

Well, to the best of my knowledge, absolute majority of Android applications don’t do it and are still doing fine. So, I think it’s not mandatory. That said, if you don’t do it you can expect to see a certain amount of errors and crashes due to spurious notifications if you get to scale. So, not mandatory, but it’s not like it’s a premature optimization. We do know that this is rare, but real issue.

I try, as much as possible, to use my own MVC architectural pattern for organization of applications’ presentation layer. In this approach I can “disconnect” from UI by writing just a single line of code. Therefore, if I get to use this pattern, I always disconnect the UI in onStop().

Update: as I wrote in the discussion of onStart(), the issue I’m talking about was fixed, so you don’t need to unregister View listeners in onStop().

onDestroyView()

You should not override this method in absolute majority of the cases. I guess some of the readers will be surprised to read this, but I really mean it.

As I said, you must assign all Fragment’s fields which are Views in onCreateView(LayoutInflater, ViewGroup, Bundle). This requirement stems from the fact that Fragment’s View hierarchy can be re-created, so any View reference you don’t initialize in that method will be either null, or point to an object from the old (now discarded) hierarchy. Doing this is very important because otherwise you might get some really nasty bugs, crashes and memory leaks.

If you follow the above recommendation for onCreateView(LayoutInflater, ViewGroup, Bundle), Fragment will hold strong references to its Views until onCreateView(LayoutInflater, ViewGroup, Bundle) will be called the next time, or the entire Fragment is destroyed. However, there is no risk of memory leak or any other potential issue in that.

Now, there is a widespread complementary recommendation that you should set all the aforementioned references to View fields to nulls in onDestroyView(). The motivation is to release these references as quickly as possible to allow garbage collector to claim them right after onDestroyView() returns. This will free the memory associated with these Views much earlier. In my opinion, that’s classic case of premature optimization. You don’t really need this optimization in absolute majority of the cases. Therefore, there is no need to complicate the already complex Fragment lifecycle handling with override of onDestroyView().

So, you don’t need onDestroyView().

onDestroy()

Just like with Activities, there is no need for you to override this method in Fragments.

onSaveInstanceState(Bundle)

The implementation of this method is essentially the same as of its Activity’s counterpart:

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
    }

I remind you not to be mislead by the seeming simplicity of this method. Incorrect handling of save & restore flow is one of the major causes for bugs and crashes in Android applications.

Javadoc of this method was written by Dianne Hackborn back in February 2011 and contains this really strange part:

This corresponds to Activity.onSaveInstanceState(Bundle) and most of the discussion there applies here as well. Note however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.

According to official documentation “this method may be called at any time before onDestroy()”. There are two major issues with this.

First of all, as Dianne explains, the View hierarchy associated with Fragment can be destroyed without actually saving the state. Therefore, if you’d restore Fragment’s state in onCreateView(LayoutInflater, ViewGroup, Bundle), then you would risk overriding the latest state with a stale one. State corruption is a very serious bug. That’s why I told you to restore the state in onCreate(Bundle) only.

The second issue is that if onSaveInstanceState(Bundle) can be called at any time before onDestroy(), then you don’t have guarantees as to when it’s safe to change Fragment’s state (e.g. replace nested Fragments).

Now, I don’t think that this description is accurate. In fact, I think that it had already been wrong when Dianne Hackborn wrote it back in 2011. The notion of “any time” implies some kind of nondeterminism or randomness which I think has never been there.

The good news is that this documentation might be inaccurate (surprisingly, inaccurate documentation can be “good news”). From review of some of AOSP and Support Libraries source it looks like the guarantees for onSaveInstanceState(Bundle) in Fragments are basically the same as in Activities. To be on the safe side, I opened this bug in Android issue tracker and you’re encouraged to star it such that we get the required clarifications faster.

setRetainInstance(boolean)

Never use retained Fragments. You don’t need them.

If you do, keep in mind that it changes Fragment’s lifecycle. So nothing you read in this article will be guaranteed to work anymore.

Update: retained Fragments became deprecated since I originally wrote this article.

Why Fragments are So Complicated

As you can see, Fragments are indeed very complicated beasts. I would even say too complicated.

The biggest issue with Fragments is that their View hierarchy can be destroyed and re-created independently of the Fragment object itself. If that wouldn’t be the case, Fragment’s lifecycle would be almost identical to Activity’s one. What was the reason for this complication? Well, obviously I don’t know the answer and can only speculate based on my limited understanding. Therefore, take the following discussion with a grain of salt.

I think that this mechanism was introduced for optimization of memory consumption. The ability to destroy Fragment’s View hierarchy allows to free some memory while the Fragment is e.g. not visible. But then the question becomes: Fragments implement support for the standard save & restore flow, why did Google invented yet another mechanism for essentially the same purpose?

I don’t know. What I do know, however, is that even FragmentStatePagerAdapter doesn’t rely on this optimization and forces Fragments to undergo complete save & restore.

As far as I’m concerned, this entire mechanism is a stunning demonstration of the dangers associated with premature optimization. It wasn’t really required, it’s not being useful and it drives the complexity of using Fragments to the astronomic heights. To me, it looks like all the additional issues that we experienced with Fragments over the years originated from the “root of all evil”:

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.Knuth, Donald

And the sad irony is that it looks like Google developers themselves don’t understand Fragment’s lifecycle anymore.

Google released LiveData Architecture Component with a serious bug described by Christophe Beyls in this post. If there would be just one developer working on this feature who would really understand Fragment’s lifecycle, that problem would become evident at design stage. It took Google several months to fix this bug. Lastly, during Google IO 18 they announced that the issue was fixed. The fix was… introduction of yet another lifecycle for Fragment’s View hierarchy.

So, if you’re using LiveData Architecture Component, now you’ll need to keep in mind that there are two distinct lifecycles associated with Fragment object.

Conclusion

Fragments are a mess. Their lifecycle is rocket science. The only thing worse than Fragments is their official documentation. Google developers don’t fully understand Fragment lifecycle by themselves and continue to increase the complexity of this beast.

Said all that, I’ve been using Fragments for years and will continue to use them in the foreseeable future. Compared to “one Activity per screen” approach, Fragments can provide better user experience and make the application feel snappy. To use Fragments and keep my sanity, I’m using the approach to Fragment’s lifecycle described in this article. It probably doesn’t cover 100% of use cases, but it worked great for me in the past years.

There are two advantages to this approach: it makes the handling of Fragment’s lifecycle very similar to Activity’s lifecycle and independent of it. You might’ve noticed that nowhere in the post I mentioned the state of the enclosing Activity. As long as I stick to this scheme, I don’t care what happens to that Activity. I don’t even care whether this Fragment is top level or nested one.

So, I override minimal number of methods and don’t have inter-dependencies between lifecycles. This makes the complexity of Fragments manageable for me. If you have anything to add or want to point out any mistake in this article – feel free to do it in the comments section below.

If you liked this post, then you'll surely like my courses

Subscribe for new posts!