Thinking in Compose
I am working on a pet project CMediaPlayer on github. I plan to write a media player that requires no additional permission since all the player, even VLC, wants full write storage permission, which I feel is silly.
The project actually started almost a year ago back in March 2022, and I was using Fragments hosting an AAC ViewModel that keeps a ExoPlayer
instance, I got it to survive rotation with video continue playing and all that. Then this jetpack compose train caught on, so I cobbled something together (i.e, a StyledVideoView
hosted via AndroidView
Composable.) that kind of works last April without knowing any better. One question remained back then - how do I make sure the ExoPlayer
instance is retained across rotation, without needing to recreate it? Since I couldn’t seem to get video to continue playback after a rotation, I felt I must be missing something with this configuration change thing.
Thinking back it was quite obvious but that question remained in my mind for a few month; I also asked in a few places but most people who think in compose came back with a very puzzled look “what are you talking about? if you have the right state exposed recomposition should take care of itself.” And I look at those answers also with a puzzled look, “I am dealing with a legacy AndroidView
here, something in legacy lifecycle must be doing something!” I tried DisposableEffect
, LifecycleEventObserver
, dirty hack of keeping something outside of composable scopes, all to find out that I cannot distinguish between a destroy due to rotation or actual destruction. I need to be able to properly clean up the ExoPlayer
object after a proper destruction! Otherwise an obvious memory leak, right?
Boy am I wrong; after looking at my code closely, there were multiple places where I was just blindly recreating new ExoPlayer
that caused video “restarts.” Removing those and video playback started to survive across rotations! So the question remain becomes “when do I call ExoPlayer.release()
to get rid of the object in memory?” Well, with androidx/compose navigation and everything hosted in a single Activity, we actually have almost full control of when we want to release it. After thinking through the user story, I realized I can call release()
when user actually leaves video playback completely! This is expressed when user closes video playback screen and that can be defined clearly in screen composables. We no longer need to tie ourselves into Fragment
/Activity
lifecycle callbacks like onStop()
or onPause()
with checks about if this is called due to activity restarts.
There are still a lot of the effect of hacking compose into a Activity/Fragment based application in my code as of this writing, and by looking at other compose example apps I think I am starting to get a feeling of the pattern. I might write about this topic more in the future but for now I just finally solved one of my biggest question with jetpack compose I can write this down so I can kind of recall it in the future…