Fragments have changed more in recent years than most other Android apis. It was originally part of the Android platform, became part of the Android Support Library, and is now part of Jetpack as a standalone AndroidX Fragments.

Tip: You should no longer need to use the Fragment in the Android framework. Aside from the fact that it will be deprecated in Android 10, it will remain in the framework for a long time, with no updates, bug fixes, or compatibility for older devices or versions of the system.

Android architectural components have taken over many of the traditional functions of fragments (such as using LifecycleObserver to listen for lifecycle callbacks or ViewModel to hold state). If you use fragments, you add, remove, and interact with them through the FragmentManager.

In Fragment 1.3.0-Alpha08 (latest version 1.3.0-RC01), a lot of refactoring has been done inside FragmentManager. This version replaces much of the logic originally implemented in FragmentManager with lighter, more testable, and easier to maintain inner classes, the core of which is the FragmentStateManager.

Tip: I’ll talk a lot about the internals of FragmentManager in this article. In short: please do regression testing specifically for Fragment 1.3.0-Alpha08 and let us know on Issue Tracker if you find any regression.

The new state manager takes care of many key aspects of fragments:

  • Move the Fragment in the lifecycle method
  • Add animations and toggle effects
  • Deal with deferred transactions

We analyzed the implementation mechanism of the original system from the bottom up and found some problems, so we rewrote the state manager. We solved a dozen existing problems. This refactoring now supports multiple return stacks in a single FragmentManager, and also simplifies the Fragment lifecycle.

FragmentManager’s moveToState() method

Each FragmentManager is associated with a host. FragmentActivity is the most prominent of most fragment uses (there are also entire layers of FragmentController and FragmentHostCallback that can be used to build custom hosts, We won’t discuss them here). As an Activity’s lifecycle moves through CREATED, STARTED, and RESUMED states, the FragmentManager passes these state changes to its fragments accordingly. That’s what moveToState() does.

Of course, it’s not as straightforward as that. There is a lot of conditional logic to control the actual state of a fragment. The lifecycle state of an Activity (or the state of a nested fragment parent) is only the first step and can serve as an upper limit on the state of the fragment. This upper bound ensures proper nesting of activities, fragments, and their child fragments.

So our first job in simplifying the moveToState() method was to aggregate this logic into one place, hence the Birth of the FragmentStateManager. Each Fragment instance is associated low-level with a FragmentStateManager. By using this class internally, we can remove much of the code that interacts with the Fragment (such as calling the Fragment’s onCreateView method and other life-cycle related methods) from the FragmentManager.

Such code separation also allows us to solve the logic needed for forward compatibility in a separate way, namely what state the Fragment should be in, and then aggregate it into one place: computeExpectedState(). This method keeps track of all current states and determines which state the Fragment should be in. Although 98% of the time a Fragment is in the same state as its host or parent Fragment, what happens to the remaining 2% has a profound impact on fragment-based applications.

However, there is one case where there is no way to determine the actual state of a Fragment: lazy-loaded fragments.

Lazy-loaded fragments

Fragments, for better or worse, inherit much of the same naming conventions and API call interfaces from activities. Part of this inheritance is about interface switching and delaying switching until the application is ready. This logic is important for application scenarios that involve switching shared elements (sometimes you want to know the image resolution and location on the screen to be loaded before the scene switches), as well as to ensure that you don’t trigger a lot of loading operations during the interface switch.

Lazy-loaded fragments have two important qualities:

  1. The view is created, but not visible;
  2. The upper limit of the life cycle is STARTED.

When you call startPostponedEnterTransition (), fragments of switching operation began, view will become visible, the state of the fragments will be RESUMED. These are implemented by the new state manager, which was not the case with the previous Fragment mechanism. For reference, we quote a related problem description here:

When a Fragment uses the postponeEnterTransition() method for lazy loading, the desired effect is to add a container to the Fragment, In fragments called startPostponedEnterTransition before (), don’t run any into the animation of the interface, or have been in the queue before exit animation (such as the replace () operation). Another expected effect is that the Fragment does not go into a RESUMED state when the container is delayed loading.

However, the FragmentManager doesn’t seem to follow this procedure, leaving the Fragment and the FragmentManager as a whole in a strange, inconsistent state.

In other words, any FragmentTransaction associated with a currently lazy-loaded Fragment will fall back to its previous state (i.e., to a previous state), but these fragments are not converted to a proper state.

This leads to a series of problems:

  • The Fragment view was created, but the Fragment was not added (isAdded() returns false)
  • FindFragmentById () will not return the Fragment you just added, even if you call commitNow()
  • When FragmentManager starts, fragments in an intermediate state gets stuck and won’t follow start (issuetracker.google.com/issues/1290…).
  • FragmentTransactions execution sequence will be upset (issuetracker.google.com/issues/1472…).
  • Container will still play other animation (for example, has played the pop-up animation) (issuetracker.google.com/issues/3714…).
  • OnCreateView () is called the second (issuetracker.google.com/issues/1439…).

In fact, solving any of these problems would require replacing the entire fallback process used by lazy-loaded fragments with a system that keeps FragmentManager in a consistent, up-to-date state while preserving some of the most important features of lazy-loaded fragments.

Operate at the container level

The FragmentManager contains a handy attribute to which you can pass the ID of the container in which the Fragment is located. Even for a single FragmentTransaction, you can add a Fragment to a container, remove another Fragment from a different container, replace the top Fragment of a third container, and so on. The interleaving of operations occurs only when the Fragment animation cuts and cuts, all at the container level.

Fragment supports multiple animation systems:

  • The old version of the Animation API has no real use
  • The Animator API in the development framework
  • The Transition API in the development framework (only supports API 21 and above, which also doesn’t work)
  • AndroidX Transition API

As you probably know, naming is a big problem in computer science, so when we were going to build a class to control all of these apis, we decided with some effort to call it SpecialEffectsController (this class is not a public API, so you can change the name in the future). This class exists at the container level and coordinates all the “special effects” associated with cutting and cutting fragments.

SpecialEffectsController is the sole source that determines future changes to the container. In other words, if the fragment added first is delayed loading, the entire container is delayed loading. There is no further logic to implement at the FragmentManager level, or any fallback operations (we mentioned that it can affect multiple containers). Therefore, the FragmentManager is in the correct state, and we also get the special properties of all lazy-loaded fragments.

Based API lets we will all the cool effect of API into separate DefaultSpecialEffectsController, it is responsible for implementing the transitions and animations and Animator. That is, to gather the logic scattered across FragmentManager into one place.

What does “New state manager” mean

What it really means is to combine the following structure:

Old state manager: All logic is contained in FragmentManager

Replace it with the following structure:

New state manager: The FragmentManager interacts with a separate FragmentStateManager instance, which in turn coordinates other fragments via SpecialEffectsController in the container

By separating FragmentManager, the overall logic has been greatly simplified at various levels:

  • FragmentManager only contains the state for all fragments
  • FragmentStateManager manages status at the Fragment level
  • SpecialEffectsController manages state at the container level

The separation of responsibilities allowed us to extend our test cases by 30%, covering more application scenarios, many of which were almost impossible to test in isolation from each other.

Will there be behavior changes that need to be addressed?

Don’t. In fact, we ran a lot of internal Fragment tests between the old and new state managers to make sure we did an adequate number of regression tests.

You can find a list of bug fixes related to the new status manager in the release log. So take a look at the list to make sure your problems are not caused by the wrong way you handled them earlier, and to remove the logic that was previously problematic.

Similar to the onDestroyView update in Fragment 1.2.0, the new state manager will remain in the STARTED state until your Fragment’s switch/animation /animator/ effects end. However, all fragments remain the same whether they are directly or indirectly lazy-loaded, because they belong to the same container.

What if behavior changes?

When you upgrade to Fragment 1.3.0-Alpha08, the new state manager is enabled by default. If you notice a change in application performance, you can first test whether the change is due to the new state manager by adding the following experimental API:

FragmentManager.enableNewStateManager(false)
Copy the code

This API helps you disable the new status manager to help you check whether the current change is related to it. It helps clear the way for you to upgrade to Fragment 1.3.0-Alpha08, please submit any questions in this Issue Tracker.

Tip: FragmentManager. EnableNewStateManager () API is experimental. This means that it is not included in the Fragment’s stable API and may be removed in the future. Removing old code is an important step in reducing code volume, but to make the process seamless and smooth, we plan to keep the API in place until stable Version 1.3.0 of Fragment is released. You might consider removing the API calling code when Fragment 1.3.1 is released.

More than 100 independent changes over 11 months resulted in the largest internal upgrade of the Fragment and brought us a more maintainable, sustainable and understandable base code. This means that fragments are more consistent, and that you can rely on a more solid base of code to build applications. We also welcome questions and feedback from all of you to help make the new state manager even better.