media icon indicating copy to clipboard operation
media copied to clipboard

Migrate the Composition demo to use the new Retain Api

Open pawaom opened this issue 3 weeks ago • 3 comments

Google recently introduced the Retain Api,

https://android-developers.googleblog.com/2025/12/whats-new-in-jetpack-compose-december.html

https://developer.android.com/reference/kotlin/androidx/compose/runtime/retain/package-summary#retain(kotlin.Function0)

this looks simple and easy to use.

the composition demo uses Viewmodel to handle the composition player , placedoverlays etc, Instead they can use the retain Api.

It will eliminate the need for the dependency injection, (which viewmodels may require), maintain the
Unidirectional Data Flow (UDF) pattern and will be easier to deploy.

pawaom avatar Dec 12 '25 08:12 pawaom

Thanks for the suggestion. This is a good idea! Also, you're welcome to make a contribution and create a pull request for this. You can find the contribution guidelines here: CONTRIBUTING.md

shahdDaghash avatar Dec 12 '25 14:12 shahdDaghash

A big problem with using this API when it comes to our library in particular is that the Player object is resource-heavy.

From the docs you provided:

Important: Retained values are held longer than the lifespan of the composable they are associated with. This can cause memory leaks if a retained object is kept beyond its expected lifetime. Be cautious with the types of data that you retain. Never retain an Android Context or an object that references a Context (including View), either directly or indirectly.

Usually we instantiate a Player like

ExoPlayer.Builder(applicationContext).build().apply {
    setMediaItems(mediaItems)
    prepare()
  }

.. but behind the scenes we do not save this context forever, but keep calling getApplicationContext(). So actually, this bit of a potentially-stale info might be ok.

It's interesting that the blogpost mentions exoplayer among other way less heavy objects:

As retain does not serialize your state, you can persist objects like lambda expressions, flows, and large objects like bitmaps, which cannot be easily serialized. For example, you may use retain to manage a media player (such as ExoPlayer) to ensure that media playback doesn’t get interrupted by a configuration change.

The fact that https://developer.android.com/reference/kotlin/androidx/compose/runtime/retain/package-summary#RetainedEffect(kotlin.Array,kotlin.Function1) talks about video players in particular is very promising, but kind of relies on mediaUri being that one thing that sets the player apart from other players. Just because you got redirected somewhere, doesn't mean you should reinitialize the whole Player object.

@Composable
fun VideoPlayer(mediaUri: String) {
    val player = retain(mediaUri) { MediaPlayer(mediaUri) }
    RetainedEffect(player) {
        player.initialize()
        onRetire { player.close() }
    }

When configuration is changing, of course ideally we would retain the Player. After all, a change of theme or a 2 second rotation shouldn't lead to a player release and recreation, along with codecs, factories and permissions.

I have seen developers retain just the minimal required information for a smooth transition - e.g. saving the current position, media item uri, volume, isPlaying state, etc. Then quickly recreating the player with all those values and preparing it. That's too slow.

On the other hand, putting a player into a ViewModel that is not tied to any particular composable is just as dangerous as this retain API - how could we ensure the view model releases the player in due time? You could link the lifecycle of the viewmodel to the lifecycle of the whole app - onCreate, onDestroy, onPause etc. But what if your app is a deeply nested navigation graph of screens and your view models are per screen? What if your user spends more of the time in part of the app that has no player view? Why would you then create a player from the start? Or if the user navigates away from the player with a backswipe - should you immediately release the player or start a timer-coroutine that will release it if the user doesn't navigate back to the page within X seconds?

These are all app-specific scenarios and we cannot provide you with an out-of-the-box solution for how to structure your view models, since we don't publish them in the library. We repeatedly mention that demos/ are not standalone apps - they are meant to have enough functionality for us to check the new apis and for you to see cohesive code snippets. And so they might not be using the most up-to-date developer guidance on how to structure your app with new features like Room database or Hilt/Dagger DI. All we can do is to remind you that mishandling of the Player lifecycle can lead to ANRs and leaks.

What to retain then?

Some examples of when content is transiently destroyed include:

  • Navigation destinations that are on the back stack, not currently visible, and not composed
  • UI components that are collapsed, not rendering, and not composed
  • Composition hierarchies hosted by an Activity that is being destroyed and recreated due to a configuration change

For the composition demo in particular, it might mean the drop down menus maybe? or navgraph once it grows to be big enough to be worth saving.

oceanjules avatar Dec 12 '25 16:12 oceanjules

Thanks for the detailed reply, the problem seems to be context.

As you mentioned the blogpost mentions we can use Exoplayer with Retain And it also uses Applicationcontext. Following this suggestion I Would suggest to consider using Retain as it is an efficient way to use Exoplayer in compose which seems to be the future of Android also it will be helpful for new device from factors such as foldables etc.

Moreover the Demo example uses Applicationcontext in ViewModel itself, there seems to be no harm is using Retain.

Moreover it seems useful for issues like these

https://github.com/androidx/media/issues/2288

pawaom avatar Dec 13 '25 09:12 pawaom