I had this issue on the first Android app I’m writing entirely in Kotlin and it drove me crazy!
I’m implementing Audio Focus in a podcast app. When a user wants to play an episode of the podcast, we need to request the audio focus passing by an implementation of OnAudioChangeListener (because we could lose the audio focus while playing if the user uses another app which also requests audio focus):
In this listener we want to react to different states:
When the episode is done playing or the user actively pauses the episode he is listening to, we need to abandon the audio focus:
The Path to Madness
As avid of new stuff as I am I decided to implement the listener, onAudioFocusChange, as a lambda function. I don’t remember if it was suggested by the IntelliJ IDE or not, but anyway it was declared as the following:
In onCreate() this variable is assigned a lambda function:
All worked fine as we can request the audio focus which would pause other apps (like Spotify) and play our episode.
Abandoning the audio focus seemed to work too as we get AUDIOFOCUS_REQUEST_GRANTED as a result when calling abandonAudioFocus on AudioManager.
But as soon as we want to request again the audio focus we are notified through the listener we immediately lost the focus by receiving AUDIO_FOCUS_LOSS:
Why are we notified of losing the audio focus after requesting it?
What the hell is going on?
Behind the scene
The best tool to help us understand the issue is the Kotlin Bytecode viewer!
Let’s have a look at what is assigned to our onAudioFocusChange listener:
We can see that lambdas are translated to generic FunctionN classes where N is the number of parameters. Its implementation is hidden here and we’ll need another tool to see exactly what is assigned but that’s another story.
Let’s check how OnAudioFocusChangeListener is really implemented:
And now let’s see how it is used. The requestAudioFocus function:
The abandonAudioFocus function:
You probably noticed the problematic line in both functions:
What really happens is that our lambda / Function1 class is initialized in onCreate() but every time we pass it as a SAM Interface to a function it gets wrapped in a new instance of a class implementing our listener interface meaning two instances of the listener are created and the AudioManager API cannot remove on abandonAudioFocus() the listener previously created and passed to the first call of requestAudioFocus() !!! As the original listener is never removed, it makes sense we get notified with AUDIO_FOCUS_LOSS on the first instance of our listener.
The right way
Listeners need to stay anonymous inner classes, so here’s the right way to declare it:
Now the same instance class implementing OnAudioChangeListener is referenced by our onAudioFocusChange variable and is passed correctly to both functions, requestAudioFocus and abandonAudioFocus on AudioManager. Yay!