- Android Fragment Lifecycle for Professional Developers
- Vasiliy Zukanov
If you ask me what are the three most complex issues in the Android framework, the Fragment lifecycle is definitely one of them.
Dianne Hackborn, the legendary Android Framwork developer who introduced The Fragment to Android in 2010, wrote in his submission:
Author: Dianne Hackborn
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 “breaking up a single Activity into separate parts” is a good one. However, the implementation and evolution of this API is not ideal in terms of the actual use of fragments today.
Fragment is by far one of the most controversial Android apis in the Android framework. Many professional Android developers won’t even use Fragments in their projects. Not because they don’t understand the benefits of fragments, but because they clearly see their drawbacks and limitations.
If you look closely at the following flow chart, you know that I’m not overstating the complexity of the Fragment life cycle. It’s scary.
Fortunately, you don’t need to understand the entire life-cycle transformation process when using Fragments in your application.
In this article, I’ll show you some ways to handle the Fragment life cycle, which hides most of the complex details. I’ve been using this method for a few years now, and it works.
The Activity life cycle
As you will see, when I use Fragments, I try to decouple them from the Activity lifecycle as much as possible. But that doesn’t change the fact that there are many similarities.
I’ve written a post about how I handle the Activity lifecycle: Android app developers, do you really understand the Activity lifecycle? . The post received very positive feedback and became one of the most popular posts on my blog in less than a month.
As I said, the Activity and Fragment life cycles are similar in many ways. So, in this article, I will draw a lot of analogies between them. But I don’t want to reinvent the wheel. Therefore, I assume that you’ve already read about the Activity lifecycle.
My Take on Fragment Lifecycle
My approach to the Fragment lifecycle aims to achieve two goals:
- Make the Fragment’s lifecycle processing logic as similar as possible to the Activity’s processing logic.
- Make the Fragment lifecycle processing logic independent of the Activity lifecycle.
Treating the Fragment life cycle in a similar way to treating the Activity life cycle can greatly reduce the overall complexity of your application. Developers need to learn only one approach, not two different approaches. This means less work during development, easier maintenance, and faster familiarity with the project for new team members. I’m also absolutely certain that doing so reduces the risk of bugs, although that’s just my opinion.
By achieving both of these goals, I greatly reduced the complexity of the issues related to fragments, thus making them more attractive.
onCreate(Bundle)
Remember, you don’t have access to the Activity’s constructor, so you can’t inject dependencies into the Activity through it. The good news is that fragments have a public constructor, and we can define even more. The bad news is that doing so would cause serious bugs, so we can’t do it.
When an Activity is forced to be destroyed and then restored automatically, Android destroys and recreates the Fragment in the process. The re-creation mechanism is implemented by calling the Fragment’s no-argument constructor using a reflected method. Therefore, if you instantiate the Fragment using a constructor with arguments and pass the dependent objects to it, all of these dependent objects will be set to NULL after saving and restoring.
Therefore, as with activities, you need to use the onCreate (Bundle) method as an alternative constructor. This is where the injection and initialization of dependent objects in the Fragment takes place.
However, unlike the Activity’s onCreate (Bundle) method, you are not allowed to perform any Android View-related operations in the Fragment’s onCreate (Bundle) method. This is important for reasons that will be elaborated on in the next section.
To summarize, the basic processing logic of the Fragment’s onCreate (Bundle) method looks like 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); } mActivityAsListener = (ActivityAsListener) requireActivity(); }Copy the code
One other thing I forgot to mention – converting an Activity to a listener also happens in onCreate (Bundle). If you prefer, this will make more sense than throwing a generic ClassCastException in onAttach (Context).
View onCreateView(LayoutInflater, ViewGroup, Bundle)
This method is unique to fragments, which is the most significant difference from the Activity lifecycle.
But it is also the source of evil for fragment-related problems. I’ll discuss its “evil” later, but remember that if you use fragments, it’s best not to underestimate the complexity of View onCreateView (LayoutInflater, ViewGroup, Bundle).
So what’s the story?
An Activity only has the same View Hierarchy during its lifecycle transition. You initialize the View Hierarchy in the Activity’s onCreate (Bundle), and it remains in the Activity for its entire life until it is collected by the garbage collector. You can manually change the composition of your Activity’s View Hierarchy, and Android won’t do anything for you.
However, a Fragment can have multiple View Hierarchies during its life cycle and the Android system decides when to replace them.
In other words, you can dynamically change the Fragment’s View hierarchy while the application is running. Now you should know why you can’t manipulate views in the Fragment’s onCreate (Bundle). The onCreate (Bundle) method is called only once after the Fragment is attached to the Activity and does not support the dynamic nature of the Fragment’s View Hierarchy.
Android calls the onCreateView(LayoutInflater, ViewGroup, Bundle) method every time a new View Hierarchy needs to be created. Your job is to create the View Hierarchy, initialize it to the correct state, and then use it as the return value of the method before it is taken over by the Android system.
The main principle to override this method is that all member variables in the Fragment that hold references to objects associated with View Hierarchy must be initialized in View onCreateView (LayoutInflater, ViewGroup, Bundle). In other words, if the Fragment’s member variables hold references to views or related objects, it is important to make sure that those member variables are initialized in this method.
The Fragment View onCreateView(LayoutInflater, ViewGroup, Bundle) method looks like this:
@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; }Copy the code
Another interesting aspect of this method is that it takes a single parameter: the Bundle that holds the state. To be honest, I find it troublesome. The Android framework developers themselves seemed unsure where to restore these states, so they injected the Bundle parameter into the method and left us to figure it out.
Do not restore state in this method. I’ll explain why not later when I introduce the onSaveInstanceState (Bundle) method.
User Boza_s6 submitted his or her feedback on the post on Reddit, and we had a very interesting discussion. The question is whether my approach causes memory leaks when using lists and adapters in fragments. Based on what has been discussed above, I think the answer to this question is clear.
If you follow the principles I share in this article, there is no risk of a memory leak. In fact, part of the reason I use this approach is to mitigate the inherent risk of Fragment memory leaks.
My rule is that every member variable associated with View Hierarchy in a Fragment must be initialized in this method, including list adapters, listeners for user interaction events, etc. The only way to keep code in the Fragment maintainable is to ensure that the method reinitializes the member variables associated with the Fragment when recreating the entire View Hierarchy.
onStart()
The Fragment method has exactly the same responsibilities and guidelines as the Activity’s onStart () method. You can read my previous post on the Activity life cycle. Android app developers, do you really understand the Activity life cycle? .
In summary, the basic processing logic of the Fragment’s onStart () method looks 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; }}Copy the code
As you can see, this method contains most of the Fragment’s functional logic. Keeping the onStart () method consistent between activities and fragments has many benefits.
onResume()
This method handles the same logic as the Activity’s onResume () method.
onPause()
This method handles the same logic as the Activity’s onPause () method.
onStop()
This method also has the same processing logic as the Activity’s onStop () method. The basic processing logic is as follows:
@Override
public void onStop() {
super.onStop();
mSomeView.setOnClickListener(null);
mFirstDependency.unregisterListener(this);
}
Copy the code
Here is an interesting and surprising line of code: mSomeView. SetOnClickListener (null). I’ve explained before why you might want to unregister the click event listener in the Activity onStop (), so I won’t repeat it here.
However, I would like to take this opportunity to answer another question: Is it necessary to unregister a listener for a click event?
As far as I know, the vast majority of Android apps don’t do this and still work fine. So, I don’t think it’s mandatory. But if you don’t, your app is likely to experience a certain number of bugs and crashes.
onDestroyView()
In most cases, you should not override this method. I think some readers will be surprised by this, but I really do.
As I mentioned earlier, you must initialize all member variables in the Fragment that hold references to views or related objects in onCreateView (LayoutInflater, ViewGroup, Bundle). This requirement comes from the fact that the Fragment’s View Hierarchy can be recreated, so any reference objects holding the View that are not initialized in this method will be set to NULL. It’s important to do this, otherwise your app could experience some very nasty bugs and crashes.
If you do this, the Fragment will hold strong references to those views until the next time the View onCreateView (LayoutInflater, ViewGroup, Bundle) method is called or the entire Fragment is destroyed.
It is now widely recommended that you set all the aforementioned member variables to Null in the onDestroyView () method. The goal is to free these references as quickly as possible to allow the garbage collector to reclaim them as soon as onDestroyView () returns, thus freeing the memory space associated with these views more quickly.
While the above explanation sounds reasonable, it’s a classic case of premature optimization. In most cases, you don’t need this optimization. Therefore, you don’t need to rewrite the onDestroyView () method, which would only complicate the already complex logic for handling the Fragment life cycle.
So, you don’t need to override the onDestroyView () method.
onDestroy()
Like an Activity, you do not need to override this method in the Fragment.
onSaveInstanceState(Bundle)
The basic processing logic for this method is as follows:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
}
Copy the code
I hope you are not misled by the apparent simplicity of this approach. Error handling of the save and restore process is one of the leading causes of bugs and crashes in Android applications. It’s no coincidence that I devoted a large portion of my previous article on the Activity lifecycle to this approach.
Therefore, handling the process of saving and restoring state in an Activity is cumbersome. You might think Fragment couldn’t be worse than that. Surprisingly, however, fragments are worse.
Dianne Hackborn introduced this method in Javadoc in February 2011, which contained some horrifying descriptions:
This method corresponds to the Activity’s onSaveInstanceState (Bundle) method, so most discussions of the Activity’s onSaveInstanceState (Bundle) also apply here. Note, however, that this method can be called at any time before onDestroy (). In many cases, a Fragment may be destroyed (for example, when the Fragment is on a fallback stack without a UI display), but its state is not saved unless the Activity to which it belongs actually needs to save its state.
If this “comment” doesn’t surprise you, then you don’t have a deep understanding of the Activity and Fragment life cycle.
According to the official documentation, this method can be called at any time before onDestroy (). There are two main problems:
-
As Dianne said, the View Hierarchy associated with a Fragment can be destroyed without actually saving its state. Therefore, if you want to restore the Fragment state in View onCreateView (LayoutInflater, ViewGroup, Bundle), you run the risk of overwriting the latest state. This is a very serious error, which is why I tell you to restore the state only in onCreate (Bundle).
-
If onSaveInstanceState (Bundle) can be called at any time before onDestroy (), you cannot guarantee when it is safe to change the state of the Fragment (such as replacing nested Fragments).
I don’t think Dianne Hackborn’s description of this approach is accurate. In fact, I think Dianne Hackborn made a mistake when she wrote this in 2011. The concept of “any time” implies some kind of uncertainty or randomness that I don’t think ever existed. Most likely, only a few factors influenced this behavior, and Dianne decided not to list them because she didn’t think they were important enough.
If so, she is clearly wrong.
If this description is correct at the time, then the Google developers who designed the framework were unaware that this behavior would cause the two problems listed above. In particular, it means that the Bundle containing the saved state is never passed into the View onCreateView (LayoutInflater, ViewGroup, Bundle) method.
setRetainInstance(boolean)
Don’t use a retained Fragment. You don’t need them.
If you do, keep in mind that it changes the Fragment’s life cycle. Then everything described in this article is invalid.
Why are fragments so complex?
As you can see, fragments are really complicated, I would even say very complicated.
The biggest problem with fragments is that their View hierarchy can be destroyed and recreated independently of the Fragment object itself. Otherwise, the Fragment lifecycle is almost identical to the Activity lifecycle.
What accounts for this complexity? Obviously I don’t know the answer, I can only guess from the bottom of my head.
I think this mechanism was introduced to optimize memory consumption. Destroying the View Hierarchy of the Fragment when it is not visible frees up some memory. But there is another question: Why would Google want to do that when Fragment supports state saving and recovery processes?
I don’t know.
However, I do know is that FragmentStatePagerAdapter not adopt this mechanism to save and restore the Fragments could state.
As far as I’m concerned, this whole mechanic is premature optimization. It doesn’t do anything and makes Fragment use more complicated.
The irony is that Google developers themselves don’t seem to understand the Fragment lifecycle.
Google released the LiveData Architecture Component, but there was a serious bug. If a developer had studied fragments carefully, they would have really understood the life cycle of fragments, and they would have discovered this problem during the design phase.
Google spent several months fixing the bug. Finally, during Google IO 18, they announced that the bug had been fixed. The solution is to introduce a different lifecycle for the Fragment’s View Hierarchy.
So, if you are using a LiveData component, now you need to remember that the Fragment object has two different life cycles. It makes me very sad.
conclusion
Ok, let’s finish this article.
It’s a mess. The only thing worse than fragments is their official documentation. Google developers don’t fully understand the Fragment lifecycle and will continue to increase its complexity.
Having said that, I’ve been using fragments and will continue to use them in the future. Fragments provide a better user experience than the “one Activity per interface” approach.
In order to use fragments while maintaining my sanity, I am using the methods described in this article for handling the Fragment lifecycle. It may not cover every situation, but it has served me well over the years.
This approach has two benefits: it makes the life cycle of dealing with fragments very similar to and independent of the Activity life cycle. You may have noticed that I did not mention the state of the Activity in this article. As long as I stick with this method, I don’t care what happens to the Activity, and I don’t even care if the Fragment is nested.
Therefore, I rewrote the smallest number of methods in my Fragment without any dependencies on each other. This makes the complexity of the Fragment manageable for me.
I believe I missed some important points in writing this article. Therefore, if you have anything to add or would like to point out any flaws in this article, feel free to mention them in the comments below.