ViewModel in Fragment Under the Hood: From ViewModelStore to Retain Fragments

How fragments manage their ViewModelStore, what FragmentManagerViewModel is, why child fragments are needed, and why Retain fragments are considered deprecated. A complete breakdown of architectural connections and call chains that allow fragments to preserve state during recreation.

22min readAndroid
Share:

Introduction

In the previous article, we examined ViewModelStore and studied the complete path from creating a ViewModel to storing it in ViewModelStore. We figured out where the ViewModelStore itself is stored, but we considered this in the context of ComponentActivity and its parent Activity.

How do things work with Fragments? In this article, we’ll answer the question:

Where are ViewModelStores stored for Fragments and how do Retain fragments survive configuration changes?

Introduction

ViewModelStore — is a class that contains a Map<String, ViewModel> collection inside itself. ViewModels are stored in this collection by key, and ViewModelStoreOwner - represented by Fragment, ComponentActivity, and NavBackStackEntry can clear them when necessary.

Fragment(s) — are UI parts that can live inside an activity or in another fragment, providing flexibility and reusability of the interface. Fragments are managed by the activity and its lifecycle, and navigation is often built on fragments using the SingleActivity approach. Direct descendants — DialogFragment, BottomSheetDialogFragment, and AppCompatDialogFragment — are used to display dialogs and bottom sheets.

Retain Fragment(@Deprecated) — is a fragment that is preserved during activity configuration changes, instead of being recreated. This is achieved by calling the setRetainInstance(true) method on Fragment, which tells the system not to destroy the fragment when recreating the activity.

Previously, the Retain Fragment mechanism was used to store data and background operations, since if the fragment is alive, all its data is alive. But now it’s considered deprecated and not recommended for use. In modern applications, it’s replaced by ViewModel.


How is ViewModelStore preserved in Fragment?

In this article, I assume that you have already familiarized yourself with the article ViewModelStore.

In the previous article, we thoroughly examined the process of preserving ViewModelStore for Activity. The call chain contained all steps to the endpoint ActivityThread and even higher.

However, in the case of Fragments, the call chain is fortunately shorter and simpler. Therefore, we’ll examine the preservation of ViewModelStore for Fragment and Retain Fragment based on the following diagram and supplement it for Fragments:

Let’s start working with fragments. In this article, we won’t dive deep into the workings of FragmentManager and transactions — instead, we’ll focus on where and how ViewModel and ViewModelStore are stored in the case of fragments.

As we know, fragments don’t exist by themselves — they run inside activities or even inside other fragments.

Let’s consider a simple example of an Activity that adds a fragment to a container (FrameLayout):

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        supportFragmentManager
            .beginTransaction()
            .add(R.id.frameLayoutContainer, FirstFragment())
            .commit()
    }
}

Important: The code in the article is intended solely for demonstration and does not claim to be best practices. Examples are simplified for better understanding.

Now, having Activity and transaction, let’s create the fragment itself and initialize ViewModel in it in the standard way:

class FirstFragment : Fragment() {

    private lateinit var viewModel: MyViewModel
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProvider.create(owner = this).get(MyViewModel::class)
    }
}

Here, as in previous examples (in the previous article), ViewModelProvider.create is used, which requires owner as a parameter. This means that the Fragment class must implement some interface that allows it to act as the owner of ViewModel. This interface is ViewModelStoreOwner, which is implemented by classes like Fragment, ComponentActivity, and NavBackStackEntry.

In the source code of the create method in ViewModelProvider, this interface is explicitly required. Since ViewModelProvider was rewritten for KMP, its expect declaration is located in commonMain:

public expect class ViewModelProvider {
    ....
    public companion object {
        public fun create(
            owner: ViewModelStoreOwner,
            factory: Factory = ViewModelProviders.getDefaultFactory(owner),
            extras: CreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
        ): ViewModelProvider
    }
}

Since we figured this out, let’s immediately look at how Fragment implements the ViewModelStoreOwner interface. This is important because classes like DialogFragment, BottomSheetDialogFragment, AppCompatDialogFragment — inherit from Fragment, and among them, only it implements this interface:


@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    if (getMinimumMaxLifecycleState() == Lifecycle.State.INITIALIZED.ordinal()) {
        throw new IllegalStateException("Calling getViewModelStore() before a Fragment "
                + "reaches onCreate() when using setMaxLifecycle(INITIALIZED) is not "
                + "supported");
    }
    return mFragmentManager.getViewModelStore(this);
}

As we can see, the fragment, to get its ViewModelStore, turns to FragmentManager and requests the needed ViewModelStore from it, passing itself as a key:

...
        return mFragmentManager.getViewModelStore(this);
...

Reminder:

FragmentManager — is the main component that manages fragments. It manages their stack and allows adding fragments to the back stack.

Next, we’re interested in the getViewModelStore method, which exists in the FragmentManager.java class:


@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    return mNonConfig.getViewModelStore(f);
}

It turns out there’s another nested call here: the getViewModelStore method is called on the mNonConfig object, where the fragment is passed as a key. Let’s see what this mNonConfig object is:

private FragmentManagerViewModel mNonConfig;

This is interesting: FragmentManager uses its own ViewModel to store information about the ViewModelStore of fragments it launched. And this is logical — it needs to somehow preserve the state of fragments and their ViewModels during configuration changes.

So, we figured out the following call stack (in order):

  1. ViewModelProvider.create(owner = this).get(MyViewModel::class)
  2. Fragment.getViewModelStore()
  3. FragmentManager.getViewModelStore(fragment)
  4. FragmentManagerViewModel.getViewModelStore(fragment)

Therefore, we’ll be interested in the FragmentManagerViewModel class next. We’ll start our path with its method call FragmentManagerViewModel.getViewModelStore(fragment):

FragmentManagerViewModel.java:

final class FragmentManagerViewModel extends ViewModel {
    ...

    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore == null) {
            viewModelStore = new ViewModelStore();
            mViewModelStores.put(f.mWho, viewModelStore);
        }
        return viewModelStore;
    }
    ...
}

How does this work? Inside FragmentManagerViewModel there’s a HashMap<String, ViewModelStore>() collection that stores ViewModelStore for each fragment belonging to the FragmentManager. That is, all fragments that were added using FragmentManager — when trying to get ViewModelStore, first search for it by key (f.mWho).

If ViewModelStore is not found — this means the fragment is creating a ViewModel inside itself for the first time, and accordingly, it needs a ViewModelStore for the first time. In this case, ViewModelStore is created and placed in the HashMap mViewModelStores.

final class FragmentManagerViewModel extends ViewModel {
    ...
    private final HashMap<String, Fragment> mRetainedFragments = new HashMap<>();
    private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
    private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
    ...
}

mViewModelStores — is a HashMap that stores ViewModelStore of all fragments located inside Activity or nested in a parent fragment. Each ViewModelStore is associated with a specific fragment by its unique key (fragment.mWho) and is used to store ViewModels bound to the lifecycle of the corresponding fragment.

What do we know at this point? When we create a ViewModel inside our Fragment, its ViewModelStore is stored inside FragmentManager, more precisely — inside its ViewModel (FragmentManagerViewModel).

Everything seems clear: our ViewModel is stored inside ViewModelStore, which itself is stored inside FragmentManagerViewModel (which is also a ViewModel). And here a logical question arises — where is FragmentManagerViewModel itself stored? It’s also a ViewModel, which means it should be stored inside some ViewModelStore.

Short answer: it’s stored inside the ViewModelStore that belongs to the Activity itself.

Want to make sure? Then read on.

To answer our question, let’s start with the basics — with how fragments work and where FragmentManager comes from. But before that, let’s look at the hierarchy of all existing types of Activity to understand which chain we’ll start working with:

Activity Hierarchy:

Activity
└── ComponentActivity
    └── FragmentActivity
        └── AppCompatActivity
ClassPurpose
ActivityBasic low-level screen class in Android SDK. Direct use is not recommended.
ComponentActivityModern foundation for Jetpack components: ViewModel, SavedState, ActivityResult API, OnBackPressedDispatcher
FragmentActivityAdds fragment support (via AndroidX). Fragments from android.app.Fragment are no longer supported.
AppCompatActivitySupport for older Android versions with Deprecated Api, AppCompatDelegate ActionBar, AppCompat themes, Material UI.

As you probably guessed, we’ll be interested in FragmentActivity specifically. FragmentActivity — is a base class that provides integration with the fragment system. It’s responsible for creating and managing FragmentManager. The more commonly used AppCompatActivity is built on top of it, extending functionality through support for components from the support library (AppCompat).

It’s FragmentActivity (or its descendant AppCompatActivity) that allows full-featured work with fragments and FragmentManager. Other ways of interacting with fragments are considered deprecated.

Let’s examine the source code of FragmentActivity:

FragmentActivity.java

public class FragmentActivity extends ComponentActivity {
    ...
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

    class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements ViewModelStoreOwner {
        ...

        public ViewModelStore getViewModelStore() {
            return FragmentActivity.this.getViewModelStore();
        }
        ...
    }
    ...
}

HostCallbacks implements many interfaces besides ViewModelStoreOwner, but they’re omitted in the article to not distract from the essence.

We see a variable mFragments that has the type FragmentController. This variable is assigned the result of calling the static method createController, where a new instance of HostCallbacks() is passed.

HostCallbacks — is a class that implements the ViewModelStoreOwner interface. In its getViewModelStore() method, it returns the ViewModelStore belonging to the FragmentActivity itself.

Additionally, HostCallbacks inherits from the FragmentHostCallback class, which looks like this:

@Suppress("deprecation")
abstract class FragmentHostCallback<H> internal constructor(
    ...
) : FragmentContainer() {

    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
    val fragmentManager: FragmentManager = FragmentManagerImpl()
    ...
}

FragmentHostCallback was rewritten from Java to Kotlin starting from version androidx.fragment:fragment:*:1.7.0-beta01.

Inside FragmentHostCallback, a FragmentManager object is created. Knowing this, we return to the source code of FragmentActivity, where there’s a field mFragments:

    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

Here a HostCallbacks object is created, which inherits from FragmentHostCallback and implements the ViewModelStoreOwner interface, ultimately returning the ViewModelStore belonging to the activity itself.

Let’s look at the source code of the static method FragmentController.createController():

public class FragmentController {

    private final FragmentHostCallback<?> mHost;

    /**
     * Returns a {@link FragmentController}.
     */
    @NonNull
    public static FragmentController createController(@NonNull FragmentHostCallback<?> callbacks) {
        return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
    }

    private FragmentController(FragmentHostCallback<?> callbacks) {
        mHost = callbacks;
    }

}

We see that inside FragmentActivity, a FragmentController is created through a call to the createController() method. The method takes a FragmentHostCallback object — in our case, this is the HostCallbacks subclass, which implements ViewModelStoreOwner and provides the ViewModelStore of the activity itself.

To better understand the chain of creation and dependency passing, let’s look at the schema:

FragmentActivity
    └── Has a → FragmentController (mFragments)
           └── Created with → HostCallbacks
                   ├── Implements → ViewModelStoreOwner (delegates to FragmentActivity)
                   └── Extends → FragmentHostCallback
                            └── Has a → FragmentManagerImpl (as fragmentManager)

This structure allows FragmentActivity to delegate fragment management to a special helper — FragmentController. Thus, FragmentActivity doesn’t deal directly with fragment logic, but maintains access to key components: FragmentManager and ViewModelStore, thanks to the helper class HostCallbacks.

Now let’s look in more detail at how FragmentController is created and initialized. Let’s pay attention to the following line:

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

Here an instance of FragmentController is created, which receives a HostCallbacks object as a parameter. This object provides the necessary dependencies, such as FragmentManager.

Next, we turn to the constructor of FragmentActivity. It calls the init method, inside which an OnContextAvailableListener listener is registered. This listener triggers when the context becomes available, and at that moment the attachHost method is called on FragmentController:

FragmentActivity.java:

public class FragmentActivity extends ComponentActivity {
    ...
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

    public FragmentActivity() {
        super();
        init();
    }

    private void init() {
        ...
        addOnContextAvailableListener(context -> mFragments.attachHost(null /*parent*/));
    }
}

Now let’s look inside the attachHost method itself, which is implemented in the FragmentController class.

FragmentController.java:

/**
 * Attaches the host to the FragmentManager for this controller. The host must be
 * attached before the FragmentManager can be used to manage Fragments.
 */
public void attachHost(@Nullable Fragment parent) {
    mHost.getFragmentManager().attachController(
            mHost, mHost /*container*/, parent);
}

Inside this method, getFragmentManager() is called on the mHost variable. This variable represents an object of type FragmentHostCallback<?>, or more precisely, its descendant — the HostCallbacks object is passed. Having obtained the FragmentManager, its attachController method is called, passing: the HostCallbacks itself as host, itself as container, and optionally — a parent fragment (in this case null).

The mHost variable itself, used inside FragmentController, looks like this:

FragmentController.java:

private final FragmentHostCallback<?> mHost;

During the initialization stage of FragmentActivity, an instance of FragmentController is created, to which fragment management is delegated. This controller receives the HostCallbacks object in its constructor, thus ensuring the connection between FragmentManager and the activity lifecycle.

We’ve already briefly looked at how this variable is initialized, but let’s briefly repeat:

The HostCallbacks class — is an inner class of FragmentActivity that inherits from FragmentHostCallback and simultaneously implements the ViewModelStoreOwner interface. When a FragmentController object is created, it receives an instance of HostCallbacks as a parameter. This object is saved in the internal field mHost of type FragmentHostCallback<?>.

Since HostCallbacks is a descendant of FragmentHostCallback, it also has access to parent methods — in particular, getFragmentManager() (more precisely, the fragmentManager field obtained through a getter). In Java, it’s called as getFragmentManager(), although in Kotlin it’s just a property. We can then pass mHost to FragmentManager methods.

Now let’s see how exactly FragmentManager gets access to FragmentManagerViewModel. This happens in the attachController method, which is called inside FragmentManager:

FragmentManager.java:

void attachController(@NonNull FragmentHostCallback<?> host,
                      @NonNull FragmentContainer container, @Nullable final Fragment parent) {
    ...
    // Get the FragmentManagerViewModel
    if (parent != null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
    ...
}

Initialization Chain: FragmentActivityHostCallbacksFragmentManagerFragmentManagerViewModel

This sequence reflects how key components of the fragment framework are created and linked together.

Now let’s break down what exactly happens inside the attachController method:


1. If parent != null

This means we’re dealing with nested fragments that are managed through childFragmentManager. In this case, FragmentManager refers to its mChildNonConfigs field, where FragmentManagerViewModels for nested fragments are stored. If the needed FragmentManagerViewModel doesn’t exist yet, it will be created and saved in the HashMap, using the parent fragment’s identifier as a key.

private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();

2. If parent == null, and host instanceof ViewModelStoreOwner

This is the main path when working with FragmentActivity and AppCompatActivity, because HostCallbacks implements ViewModelStoreOwner. In this case, FragmentManager gets the ViewModelStore bound to FragmentActivity and passes it to FragmentManagerViewModel.getInstance().

Thus, FragmentManagerViewModel is saved in the same ViewModelStore as the rest of the Activity’s ViewModels, and will live as long as the Activity itself.


3. If host doesn’t implement ViewModelStoreOwner

This is a deprecated scenario when Activity directly inherits from Activity or ComponentActivity, bypassing FragmentActivity/AppCompatActivity.

In this case, FragmentManager creates FragmentManagerViewModel without using ViewModelStore. Such a ViewModel is preserved through the NonConfigurationInstances mechanism, which Android applied before the appearance of architectural components.

This approach is no longer recommended, and with modern androidx.fragment.app.Fragment it doesn’t work. It’s applicable only for old android.app.Fragment and only with the active setRetainInstance(true) flag.

When we add a fragment to activity through supportFragmentManager, we always fall under the second condition described above: host instanceof ViewModelStoreOwner. In this situation, FragmentManager gets the ViewModelStore from host (i.e., FragmentActivity) and passes it to the FragmentManagerViewModel.getInstance() method.

Inside this method, a FragmentManagerViewModel is created, which is saved in the ViewModelStore. As we already said earlier, this ViewModelStore belongs to FragmentActivity (or its descendant AppCompatActivity).

FragmentManagerViewModel.java:


@NonNull
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
    ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore, FACTORY);
    return viewModelProvider.get(FragmentManagerViewModel.class);
}

Now let’s collect the entire chain of steps that is executed when creating a ViewModel inside a fragment:

viewmodel = ViewModelProvider(owner = this).get(MyViewModel::class)
  1. ViewModelProvider requests the ViewModelStore from the ViewModelStoreOwner. In this case owner = this, and this is a fragment.

  2. getViewModelStore() is called on the fragment since it implements the ViewModelStoreOwner interface.

  3. Inside Fragment.getViewModelStore(), a call is made to the FragmentManager in which this fragment is registered. Call: FragmentManager.getViewModelStore(fragment).

  4. FragmentManager delegates further and refers to its ViewModel — FragmentManagerViewModel.

  5. Inside FragmentManagerViewModel.getViewModelStore(fragment), a search for ViewModelStore by fragment.mWho occurs in HashMap<String, ViewModelStore>.

  6. If ViewModelStore already exists, it’s returned. If not — a new one is created, saved in the map and returned.

FragmentActivity


HostCallbacks (inherits from FragmentHostCallback & ViewModelStoreOwner)


FragmentController


FragmentManager.attachController(...)

     ├─ If there's parent:Fragment → use its childFragmentManager

     └─ If host is ViewModelStoreOwner → take ViewModelStore from host (Activity)


      FragmentManagerViewModel (ViewModel, stored in Activity's ViewModelStore)


    ┌─────────────────────────────────────────────┐
    │ HashMap<String, ViewModelStore>             │
    │ └─ key: fragment.mWho
    │ └─ value: ViewModelStore of specific Fragment │
    └─────────────────────────────────────────────┘


     ViewModelProvider(fragment).get(MyViewModel::class)

In simplified form, the diagram below illustrates how the interaction between Activity, FragmentManager, and ViewModelStore is structured. Note that this diagram is a continuation of the diagram that was at the beginning of the article.

We have an Activity that inherits from FragmentActivity (and more often — from its extended descendant AppCompatActivity). When creating an Activity, FragmentController is initialized, which receives FragmentHostCallback — more precisely, its descendant HostCallbacks.

HostCallbacks implements the ViewModelStoreOwner interface, but doesn’t create a new ViewModelStore, but returns an already existing one — the one that belongs to the Activity.

Next, FragmentController attaches FragmentManager to its host (Activity or ParentFragment). FragmentManager creates FragmentManagerViewModel and saves it in the ViewModelStore provided by HostCallbacks, i.e., — in the ViewModelStore belonging to the Activity.

Now, when inside the Activity we add a fragment through supportFragmentManager, initializing MyViewModel in the fragment leads to ViewModelProvider requesting its ViewModelStore from the fragment.

The fragment, in turn, refers to its FragmentManager“give me my ViewModelStore. FragmentManager, having a direct reference to FragmentManagerViewModel, requests a ViewModelStore from it by key (usually this is fragment.mWho) — and returns the ViewModelStore associated with this fragment.

It’s there, in this ViewModelStore, that MyViewModel will be placed.

Finally, let’s make sure that the FragmentManagerViewModel bound to the FragmentManager of the activity is indeed stored inside the ViewModelStore that belongs to the activity itself. For this, in the onCreate() method, we can log all the keys contained in the activity’s viewModelStore:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    supportFragmentManager
        .beginTransaction()
        .add(R.id.frameLayoutContainer, FirstFragment())
        .commit()

    Log.d("MainActivity", "onCreate: ${viewModelStore.keys()}")
    // Output: onCreate: [androidx.lifecycle.ViewModelProvider.DefaultKey:androidx.fragment.app.FragmentManagerViewModel]
}

Screenshot: FragmentManagerViewModel key registered in Activity’s ViewModelStore Screenshot

At this stage, we have completely traced the entire flow for the case when we have an Activity on top of which a Fragment is launched, and inside this fragment a ViewModel is initialized. We’ve reached the final point — we’ve seen exactly where the ViewModels are stored.

Nested Fragments and childFragmentManager

One important case remains — nested fragments. That is, the situation when we launch one Fragment inside another using childFragmentManager. So far we’ve only considered adding fragments through the activity’s FragmentManager (supportFragmentManager).

Recall that we already encountered this case when analyzing the attachController() method, which implements the logic for selecting the source of FragmentManagerViewModel.

FragmentManager.java:

void attachController(@NonNull FragmentHostCallback<?> host,
                      @NonNull FragmentContainer container,
                      @Nullable final Fragment parent) {
    ...
    // Getting FragmentManagerViewModel
    if (parent != null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
    ...
}

In the case when we add a fragment on top of another fragment through childFragmentManager, creating nesting, the first condition is triggered, namely — the check parent != null. We’ve already figured out in which cases this condition is met, but for understanding, let’s duplicate it once more:

If parent != null

This means that we are dealing with nested fragments that are managed through childFragmentManager. In this case, the FragmentManager refers to its field mChildNonConfigs, where FragmentManagerViewModel for nested fragments are stored. If the needed FragmentManagerViewModel doesn’t exist yet, it is created and saved in the HashMap, using the parent fragment’s fragment.mWho as the key.

In this case, the FragmentManager refers to the parent, calls the getChildNonConfig method on it, and ends up in the following code: FragmentManager.java:


@NonNull
private FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
    return mNonConfig.getChildNonConfig(f);
}

Here mNonConfig is the FragmentManagerViewModel bound to the parent FragmentManager. It calls getChildNonConfig(f), and the following happens in FragmentManagerViewModel.java

final class FragmentManagerViewModel extends ViewModel {

    private final HashMap<String, Fragment> mRetainedFragments = new HashMap<>();
    private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
    private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

    @NonNull
    FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
        FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
        if (childNonConfig == null) {
            childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
            mChildNonConfigs.put(f.mWho, childNonConfig);
        }
        return childNonConfig;
    }
}

In this method, we try to get the FragmentManagerViewModel for the parent fragment’s childFragmentManager, so that the childFragmentManager has its own FragmentManagerViewModel, in which we can store the ViewModelStore of all fragments that will be launched inside the childFragmentManager.

If such a FragmentManagerViewModel doesn’t exist yet, it is created, put in mChildNonConfigs, and then returned back to the attachController method, where it continues to be used for initializing the childFragmentManager.

Great question. Here’s how to formulate this concisely and clearly as a conclusion or reflective block — with an explanation about the FragmentManagerViewModel tree and how it’s built:


How the FragmentManagerViewModel Tree is Formed

To understand the complete picture, it’s important to visualize how the FragmentManagerViewModel hierarchy is built in a real application:

  • Initially, we have an Activity that has a FragmentManager (most often this is supportFragmentManager).
  • This FragmentManager creates its own FragmentManagerViewModel. It is stored inside the ViewModelStore that belongs to the Activity itself.

Now, if we add fragments through the FragmentManager that belongs to the Activity (supportFragmentManager), then for each such fragment a separate ViewModelStore will be created. These ViewModelStore will be stored inside the FragmentManagerViewModel associated with the Activity’s FragmentManager, in the FragmentManagerViewModel#mViewModelStores field:

final class FragmentManagerViewModel extends ViewModel {
    ...
    private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
    ...
}

Each such fragment, in turn, also has its own FragmentManager — this is the childFragmentManager. It is used if we want to launch other fragments inside the fragment (nesting, local navigation stack).

  • The childFragmentManager must also have its own FragmentManagerViewModel (like all FragmentManagers), so that it can store the ViewModelStore for fragments launched inside the parent fragment, that is, inside it.
  • These FragmentManagerViewModel are stored in mChildNonConfigs — this is a Map<String, FragmentManagerViewModel> inside the parent’s FragmentManagerViewModel.

Thus, a tree is formed:

  • The root is the FragmentManagerViewModel bound to the Activity’s FragmentManager and stored in its ViewModelStore.
  • Further — FragmentManagerViewModel for each nested childFragmentManager, saved inside mChildNonConfigs.
  • This tree can be arbitrarily deep, repeating the nesting structure of fragments in the application. Each node in this tree is a FragmentManagerViewModel

This structure allows proper ViewModel management, preserving them through configuration changes and ensuring a lifecycle tied to a specific fragment.

I think now the entire ViewModelStore storage flow should be completely clear.

If we have a FragmentActivity or AppCompatActivity, then it has its own ViewModelStore. When we add a fragment through its FragmentManager, a separate ViewModelStore is created for this fragment. This ViewModelStore will be stored inside the FragmentManagerViewModel, which, in turn, lies inside the ViewModelStore belonging to the activity.

FragmentManagerViewModel is created automatically when initializing the FragmentManager and is registered as a regular ViewModel in the ViewModelStoreOwner (for example, in the activity). It is designed specifically for storing ViewModelStores of all child fragments.

If we add another fragment at the same level — everything repeats: new ViewModelStore → in FragmentManagerViewModel → in the activity’s ViewModelStore.

But the trick is that each fragment has its own childFragmentManager, that is, it can be a container for other fragments. And childFragmentManager, like any FragmentManager, has its own FragmentManagerViewModel.

With each call to getChildFragmentManager(), the framework creates or uses an already existing FragmentManagerViewModel. This ensures that even when a fragment is recreated, the ViewModelStore of nested fragments is not lost.

This means: when adding nested fragments, the ViewModelStore of each of them will be stored in the internal FragmentManagerViewModel belonging to the parent fragment’s childFragmentManager.

Inside FragmentManagerViewModel, Fragment.mWho keys are used to save and then correctly restore the correspondence between Fragment and its ViewModelStore.

The deeper the nesting, the more the tree grows.

For example:

Activity
└── ParentFragment1
    └── ParentFragment2
        ├── ChildFragment1
        └── ChildFragment2

In such a tree:

  • ChildFragment1 and ChildFragment2 — their ViewModelStore are stored in the FragmentManagerViewModel belonging to the childFragmentManager of ParentFragment2.
  • ParentFragment2 — its ViewModelStore is stored in the FragmentManagerViewModel belonging to the childFragmentManager of ParentFragment1.
  • ParentFragment1 — its ViewModelStore lies in the FragmentManagerViewModel from the activity’s supportFragmentManager.
  • And the FragmentManagerViewModel from supportFragmentManager itself is stored in the ViewModelStore of the activity itself.

This flow helps preserve ViewModel even with complex navigation and fragment nesting.

Why all this complexity? This flow helps preserve ViewModel even with complex navigation and deep fragment nesting. This structure maintains the ViewModelStore hierarchy, ensuring correct ViewModel restoration even when components are recreated.

So, we’ve examined the entire ViewModelStore storage flow for fragments. Time to move forward, because the topic of Retain-fragments remains unexplored — so let’s move to the next part of the article.

How do Retain-fragments survive configuration changes?

I’ll remind you again: Retain-fragments have been deprecated for quite a long time, and their use is not recommended in practice. Developers who still wrote in Java remember them well — Retain-fragments existed long before the appearance of ViewModel. When ViewModel became the standard, Retain-fragments were officially declared deprecated. But it’s still useful to know about them. So, let’s begin.

At the beginning of the article, a definition was already given for Retain-fragments. And when analyzing the “internals” of FragmentManagerViewModel, attentive eyes might have noticed something related to Retain-fragments — namely, this code block that has appeared in the article more than once:

final class FragmentManagerViewModel extends ViewModel {
    ...
    private final HashMap<String, Fragment> mRetainedFragments = new HashMap<>();
    private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
    private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
    ...
}

There are three fields here. We’ve already analyzed two of them in detail:

  • mViewModelStores — for storing ViewModelStore at one level in the tree,
  • mChildNonConfigs — for storing nested FragmentManagerViewModel corresponding to child fragments / FragmentManager.

But there’s a field that we haven’t paid attention to yet — this is the topmost one: mRetainedFragments. This is a collection that stores fragments by key. Wait… what? Fragments inside a ViewModel?! Exactly so.

All fragments that have the setRetainInstance(true) flag set end up exactly there. Intrigued? Then let’s dive deeper.

How to create a Retain Fragment? Retain-fragments are not some separate class, inheriting from Fragment. This is the same old good Fragment, but with the setRetainInstance flag activated:

class MyFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setRetainInstance(true)
    }
}

Since Retain-fragments are deprecated, the setRetainInstance method is also marked with the @Deprecated annotation.

From this moment our fragment becomes Retain, and it will be able to survive configuration changes — following the same scheme by which ViewModel survives. How exactly? We already know a bit, but let’s still trace the entire path — from calling setRetainInstance() to storage inside FragmentManagerViewModel#mRetainedFragments.

For this, let’s look at the source code of the setRetainInstance method: Fragment.java:


@Deprecated
public void setRetainInstance(boolean retain) {
    ...
    if (retain) {
        mFragmentManager.addRetainedFragment(this);
    } else {
        mFragmentManager.removeRetainedFragment(this);
    }
    ...
}

The logic is simple: if the retain flag is set to true, the fragment is passed to the FragmentManager as Retain — through the addRetainedFragment method. If false — on the contrary, it is removed from the list of Retain-fragments through removeRetainedFragment.

Let’s continue and look at the FragmentManager itself and consider its addRetainedFragment method:

FragmentManager.java:

void addRetainedFragment(@NonNull Fragment f) {
    mNonConfig.addRetainedFragment(f);
}

As usual, the method passes control to mNonConfig, which, as we already know, is an instance of FragmentManagerViewModel.

FragmentManager.java:

private FragmentManagerViewModel mNonConfig;

Now let’s look at the addRetainedFragment method inside FragmentManagerViewModel:

FragmentManagerViewModel.java:

void addRetainedFragment(@NonNull Fragment fragment) {
    ...
    if (mRetainedFragments.containsKey(fragment.mWho)) {
        return;
    }
    mRetainedFragments.put(fragment.mWho, fragment);
    ...
}

That’s it: we’ve figured out how a fragment becomes Retain and how its storage works in FragmentManagerViewModel.

Now let’s consider the method for removing a fragment from the Retain-list, which works on a similar principle — through the same flow: Fragment -> FragmentManager -> FragmentManagerViewModel:

FragmentManagerViewModel.java:

void removeRetainedFragment(@NonNull Fragment fragment) {
    ...
    boolean removed = mRetainedFragments.remove(fragment.mWho) != null;
    ...
}

It remains to understand how these fragments are then restored after configuration changes, storing them alone is not enough because they need to be returned back after the Activity is recreated, all fragments are recreated, FragmentManager too, but Retain fragments should not be recreated, but should be taken from mRetainedFragments, we already saw at the beginning of the article the attachController method of FragmentManager:


@SuppressLint("SyntheticAccessor")
void attachController(@NonNull FragmentHostCallback<?> host,
                      @NonNull FragmentContainer container, @Nullable final Fragment parent) {

    ...

    if (savedInstanceState != null) {
        restoreSaveStateInternal(savedInstanceState);
    }
    ...
}

We see that there’s a call to the restoreSaveStateInternal method:

void restoreSaveStateInternal(@Nullable Parcelable state) {
    ...
    Bundle bundle = (Bundle) state;
    ...
    FragmentManagerState fms = bundle.getParcelable(FRAGMENT_MANAGER_STATE_KEY);
    ...

    for (String who : fms.mActive) {
        ...
        Fragment retainedFragment = mNonConfig.findRetainedFragmentByWho(fs.mWho);
        ...
        mFragmentStore.makeActive(fragmentStateManager);
        ...
    }
}

We’re interested in this line, another call to mNonConfig:

Fragment retainedFragment = mNonConfig.findRetainedFragmentByWho(fs.mWho);

And here’s the findRetainedFragmentByWho method itself inside FragmentManagerViewModel:


@Nullable
Fragment findRetainedFragmentByWho(String who) {
    return mRetainedFragments.get(who);
}

Thus, when restoring the FragmentManager and recreating the Activity, Retain-fragments survive this recreation: they are detached, and after restoring the FragmentManager and Activity — they are reattached.

Earlier I mentioned that Retain-fragments existed before the appearance of ViewModel. But in the current implementation, we see that they survive Activity recreation thanks to storage in FragmentManagerViewModel, and that’s exactly where they are maintained. But how did they work before the appearance of ViewModel in Android?

Let me briefly remind you: this was during the time of android.app.Fragment. Now they are deprecated and replaced with androidx.fragment.app.Fragment. In the old implementation, the mechanism resembled working with NonConfigurationInstances. If briefly, then for Retain-fragments in android.app.Fragment the following mechanism was used — they were stored here:

@Deprecated
public class FragmentManagerNonConfig {
    private final List<Fragment> mFragments;
    private final List<FragmentManagerNonConfig> mChildNonConfigs;

    FragmentManagerNonConfig(List<Fragment> fragments,
    List<FragmentManagerNonConfig> childNonConfigs)
    {
        mFragments = fragments;
        mChildNonConfigs = childNonConfigs;
    }

    /**
     * @return the retained instance fragments returned by a FragmentManager
     */
    List<Fragment> getFragments()
    {
        return mFragments;
    }

    /**
     * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
     */
    List<FragmentManagerNonConfig> getChildNonConfigs()
    {
        return mChildNonConfigs;
    }
}

Further, the FragmentManagerNonConfig object was stored inside NonConfigurationInstances in the fragments field and survived configuration changes exactly according to the same scheme that we already examined in the first article:

public class Activity extends ContextThemeWrapper ...{

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}
}

We briefly examined this mechanism because it represents a triple deprecation:

  • android.app.Fragment themselves are deprecated and were replaced with androidx.fragment.app.Fragment;
  • the concept of Retain-fragments, which allowed fragments to survive Activity recreation, is deprecated, and now instead of it, it’s recommended to use ViewModel;
  • the way these fragments are stored through FragmentManagerNonConfig is also deprecated — it was replaced by a more modern mechanism using FragmentManagerViewModel, despite the fact that the concept of Retain-fragments is no longer considered relevant.

Thus, this is not just a deprecated implementation, but an entire chain of three deprecated technologies that were completely reworked in modern versions of Android.

That’s probably all. In this article, we’ve examined some related aspects and intersections, let’s summarize.


ViewModel in Fragment

MyViewModel -> ViewModelStore -> FragmentManagerViewModel -> ViewModelStore(Activity's) ->
ComponentActivity.NonConfigurationInstances -> Activity.NonConfigurationInstances ->
ActivityThread.ActivityClientRecord

The modern way of storing states in Fragment is based on using ViewModel, which is placed in ViewModelStore. This storage is managed through FragmentManagerViewModel. In turn, FragmentManagerViewModel is bound to the activity’s ViewModelStore, which saves it in NonConfigurationInstances. This chain allows preserving the fragment’s state even during configuration changes, avoiding recreation of objects that are critical for long-term data storage.


RetainFragment in androidx.fragment.app.Fragment

MyRetainFragment -> FragmentManagerViewModel -> ViewModelStore(Activity's) ->
ComponentActivity.NonConfigurationInstances -> Activity.NonConfigurationInstances ->
ActivityThread.ActivityClientRecord

The term RetainFragment in androidx.fragment.app.Fragment is more of a remnant of older API versions. In modern androidx implementations, fragments with state preservation through setRetainInstance(true) are actually no longer recommended for use. Instead, state management moved to ViewModel, which is synchronized with the fragment’s lifecycle through FragmentManagerViewModel. Preservation occurs in the activity’s ViewModelStore, which, as in the first case, ends up in NonConfigurationInstances when the Activity is recreated. Thus, RetainFragment in the classical understanding is no longer used, its role has been completely taken over by the Fragment + ViewModel combination.


RetainFragment in android.app.Fragment (deprecated mechanism)

MyRetainFragment -> FragmentManagerNonConfig -> Activity.NonConfigurationInstances ->
ActivityThread.ActivityClientRecord

In the old Android implementation, when android.app.Fragment was used, the fragment recreation mechanism was implemented through FragmentManagerNonConfig. RetainFragment objects were placed in a special container that was saved in NonConfigurationInstances. When the activity was recreated, this structure was restored from ActivityClientRecord in ActivityThread. This mechanism is now completely deprecated and was replaced with using ViewModel, as this is a more reliable and flexible way to save data during configuration changes.


Summary

The evolution of state storage mechanisms in Fragment has gone through several stages:

  1. android.app.Fragment with FragmentManagerNonConfig → completely deprecated, no longer supported.
  2. RetainFragment in androidx.fragment.app.Fragment → no longer recommended, replaced by combination with ViewModel.
  3. Modern approachViewModelStore inside FragmentManagerViewModel, which is directly tied to the fragment’s lifecycle and saved in Activity.

Now instead of deprecated concepts, it’s recommended to use regular fragments paired with ViewModel, which makes the code more predictable and easily maintainable.


Discussion

Comments