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.
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 Fragment
s? In this article, we’ll answer the question:
Where are ViewModelStore
s stored for Fragment
s 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 Fragment
s, 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):
ViewModelProvider.create(owner = this).get(MyViewModel::class)
Fragment.getViewModelStore()
FragmentManager.getViewModelStore(fragment)
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 ViewModel
s 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
Class | Purpose |
---|---|
Activity | Basic low-level screen class in Android SDK. Direct use is not recommended. |
ComponentActivity | Modern foundation for Jetpack components: ViewModel , SavedState , ActivityResult API , OnBackPressedDispatcher |
FragmentActivity | Adds fragment support (via AndroidX). Fragments from android.app.Fragment are no longer supported. |
AppCompatActivity | Support 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 besidesViewModelStoreOwner
, 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: FragmentActivity
→ HostCallbacks
→ FragmentManager
→ FragmentManagerViewModel
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 FragmentManagerViewModel
s 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)
ViewModelProvider
requests theViewModelStore
from theViewModelStoreOwner
. In this caseowner = this
, and this is a fragment.getViewModelStore()
is called on the fragment since it implements theViewModelStoreOwner
interface.Inside
Fragment.getViewModelStore()
, a call is made to theFragmentManager
in which this fragment is registered. Call:FragmentManager.getViewModelStore(fragment)
.FragmentManager
delegates further and refers to its ViewModel —FragmentManagerViewModel
.Inside
FragmentManagerViewModel.getViewModelStore(fragment)
, a search forViewModelStore
byfragment.mWho
occurs inHashMap<String, ViewModelStore>
.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
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 ViewModel
s 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 aFragmentManager
(most often this issupportFragmentManager
). - This
FragmentManager
creates its ownFragmentManagerViewModel
. It is stored inside theViewModelStore
that belongs to theActivity
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 ownFragmentManagerViewModel
(like allFragmentManager
s), so that it can store theViewModelStore
for fragments launched inside the parent fragment, that is, inside it. - These
FragmentManagerViewModel
are stored inmChildNonConfigs
— this is aMap<String, FragmentManagerViewModel>
inside the parent’sFragmentManagerViewModel
.
Thus, a tree is formed:
- The root is the
FragmentManagerViewModel
bound to theActivity
’sFragmentManager
and stored in itsViewModelStore
. - Further —
FragmentManagerViewModel
for each nestedchildFragmentManager
, saved insidemChildNonConfigs
. - 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 theFragmentManager
and is registered as a regularViewModel
in theViewModelStoreOwner
(for example, in the activity). It is designed specifically for storingViewModelStore
s 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 existingFragmentManagerViewModel
. This ensures that even when a fragment is recreated, theViewModelStore
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 betweenFragment
and itsViewModelStore
.
The deeper the nesting, the more the tree grows.
For example:
Activity
└── ParentFragment1
└── ParentFragment2
├── ChildFragment1
└── ChildFragment2
In such a tree:
ChildFragment1
andChildFragment2
— theirViewModelStore
are stored in theFragmentManagerViewModel
belonging to thechildFragmentManager
ofParentFragment2
.ParentFragment2
— itsViewModelStore
is stored in theFragmentManagerViewModel
belonging to thechildFragmentManager
ofParentFragment1
.ParentFragment1
— itsViewModelStore
lies in theFragmentManagerViewModel
from the activity’ssupportFragmentManager
.- And the
FragmentManagerViewModel
fromsupportFragmentManager
itself is stored in theViewModelStore
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 storingViewModelStore
at one level in the tree,mChildNonConfigs
— for storing nestedFragmentManagerViewModel
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 withandroidx.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 useViewModel
; - the way these fragments are stored through
FragmentManagerNonConfig
is also deprecated — it was replaced by a more modern mechanism usingFragmentManagerViewModel
, 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:
- android.app.Fragment with
FragmentManagerNonConfig
→ completely deprecated, no longer supported. - RetainFragment in
androidx.fragment.app.Fragment
→ no longer recommended, replaced by combination withViewModel
. - Modern approach —
ViewModelStore
insideFragmentManagerViewModel
, which is directly tied to the fragment’s lifecycle and saved inActivity
.
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
No comments yet. Be the first to share your thoughts!