In Jetpack Compose, a Composition is a tree-like structure describing the UI of your app and is produced by running composables. When the Composition is no longer needed, state will no longer be tracked by Jetpack Compose, and the Composition gets disposed so that resources can be released.
ViewCompositionStrategy defines when the Composition should be disposed. The default,
ViewCompositionStrategy.Default, disposes the Composition when the underlying
ComposeView detaches from the window, unless it is part of a pooling container such as a
RecyclerView. However, if you are incrementally adding Compose in your codebase, this behavior may cause state loss in some scenarios. For example, if you are seeing weird glitches like scroll positions getting reset in your Fragment-based Compose app, perhaps you are using the wrong
ViewCompositionStrategy (you should be using one of the Lifecycle-based strategies instead).
In this blog post, I’ll cover what ViewCompositionStrategy is, why it’s needed, and how you can pick the right strategy for your use case to avoid state loss.
* Note: ComposeView is mentioned for simplicity’s sake, though the same behaviors apply for different forms of
For a more in-depth understanding, keep reading!
Disposing the Composition with ViewCompositionStrategy
ViewCompositionStrategy affects the disposal phase of the Composition by automatically disposing the Composition when certain conditions are met. Once the Composition is disposed, resources are cleaned up and state will no longer be tracked by Compose.
The specific strategy applied will determine when the Composition should be disposed automatically. Without a strategy, you would have to explicitly call
disposeComposition on the
ComposeView to dispose the underlying Composition.
Thankfully, a default strategy as defined by
ViewCompositionStrategy.Default, which is currently set to
DisposeOnDetachedFromWindowOrReleasedFromPool, is already applied when you create a
ComposeView (or call
setContent from a
ComponentActivity) so in a vast majority of cases, you won’t have to set it explicitly. However, you can change the default to a different strategy by providing it via
Compose-only vs mixed View/Compose apps
In a single-Activity Compose-only app, only one Composition is typically active. I mention typically because there are some exceptions to this — like subcomposition — but that’s out of scope for this blog post. Initial Composition occurs when the Activity is created. It runs the composables provided within
setContent, and the Composition remains active until the Compose content is detached from the window — this detachment happens when the Activity is being destroyed. This is the default
ViewCompositionStrategy of a
ComposeView (more on this below), and in a Compose-only app, this behavior is what you want.
Each instance of a
ComposeView maintains its own separate Composition. So, if you are incrementally migrating your View-based app to Compose, you may have multiple Compositions. For example, if you have a
ViewPager2 paging through Fragments and each Fragment’s content is in Compose, each
ComposeView would be a separate Composition.
The interaction between each Composition, and components with a
Lifecycle such as an Activity or Fragment, is the reason why you may have to change the default
ViewCompositionStrategy so that you are disposing at the right time.
Different ViewCompositionStrategy types
When the strategy is set to
DisposeOnDetachedFromWindow, the underlying Composition will be disposed when:
ComposeViewdetaches from the window
So when does View detachment occur?
Generally, this happens when the View is going off screen and is no longer visible to the user. Some instances include:
- When the View is removed from the View hierarchy via
- When the View is part of a transition
- When the containing Activity is being destroyed — after
onStop, but before
Note that you can listen to window attach/detach events by setting a
Before Compose UI version 1.2.0-beta02, this strategy was the default strategy as it is the preferred strategy for a majority of use cases. However, since version 1.2.0-beta02, this default has been replaced by
ComposeView is used within a pooling container, such as a
RecyclerView, View elements are constantly being attached and reattached to the window as elements are recycled as the UI scrolls. This means if you use
DisposeOnDetachedFromWindow, the underlying Composition of
ComposeViews would also constantly undergo initial Composition and disposals. Frequent disposing and recreating Compositions can hurt scrolling performance, especially when quickly flinging through the list.
To improve upon this,
DisposeOnDetachedFromWindowOrReleasedFromPool disposes the Composition when:
the ComposeView detaches from the window, unless it is part of a pooling container such as a
RecyclerView. When the Composition is within a pooling container, it will dispose when either the underlying pooling container itself detaches from the window, or when the item is being discarded (i.e. when the pool is full).
In other words,
DisposeOnDetachedFromWindowOrReleasedFromPool is like
DisposeOnDetachedFromWindow but with added functionality.
If you are curious about how this works and why it was introduced, check out Jetpack Compose Interop: Using Compose in a RecyclerView.
When the strategy is set to
LifecycleOwner must be provided and the underlying Composition will dispose when:
Lifecycleis destroyed. This strategy is appropriate when the
ComposeViewshares a 1–1 relationship with a known
For instance, the snippet below disposes the Composition when a Fragment’s lifecycle is destroyed:
This strategy is beneficial in circumstances where you want to tie the Composition’s lifecycle to a known Lifecycle. The canonical example of this is a Fragment View wherein the View can be detached from the window (that is, the Fragment is no longer visible in the screen), and the Fragment might not be destroyed yet (
onDestroy not yet called). This can happen on a
ViewPager2’s Fragment Views as you page through content. If you were to use either of the previous strategies, the Composition would be disposed prematurely, resulting in potential state loss (for example, scroll state in a LazyColumn would not be remembered).
A question you might ask yourself is this: “What if I have a
ComposeView as an item in a
RecyclerView that is within a Fragment? Which strategy should I use?” The immediate ancestor will dictate which strategy to apply — so since the
ComposeView is an item in a
RecyclerView, you would use
DisposeOnDetachedFromWindowOrReleasedFromPool, otherwise, use
A related but alternative to the previous strategy is
DisposeOnViewTreeLifecycleDestroyed. This strategy can be used if it is desired to tie the Composition lifecycle with a Lifecycle object, but the
Lifecycle is not known yet. The underlying Composition will dispose when:
ViewTreeLifecyleOwnerof the next window the View is attached to is destroyed. This strategy is appropriate when the
ComposeViewshares a 1–1 relationship with their closest
ViewTreeLifecycleOwner, such as a Fragment View.
For instance, the snippet below shows a custom View that inherits from
AbstractComposeView. The Composition will be disposed when the closest
ViewTreeLifecycleOwner is destroyed as the
ViewCompositionStrategy is modified to
Essentially, this works by finding the associated
LifecycleOwner responsible for managing the
ComposeView by using the
A question you might have is, when should I use
DisposeOnViewTreeLifecycleDestroyed? If the
Lifecycle object is already known, then use
DisposeOnLifecycleDestroyed; otherwise, use
We covered all the different types of
ViewCompositionStrategy options to use and how selecting the right one in an interop scenario is important to properly dispose of the Composition. See the table in the introduction of the post as a reference for when you should use what.
Have any questions? Leave a comment below!