I am trying to get LiveData
updates in a ViewModel
, and make sure that the observer is not leaking, but it is leaking. The typical problem is that the observer is not stored in a variable, but that is not the case here; the lambda is stored in a variable.
private val observer: (List<MusicFile>) -> Unit =
{ musicFiles: List<MusicFile> ->
_uiState.postValue(FragmentMusicListState(musicFiles))
}
init {
musicRepository.musicFiles.observeForever(observer)
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public override fun onCleared() {
super.onCleared()
musicRepository.musicFiles.removeObserver(observer)
}
The problem is that after onCleared is called, the observer is still attached. I verified this with the following test.
@Test
fun onCleared_RemovesObserver() {
val musicRepository = MusicRepository.getInstance()
val context = InstrumentationRegistry.getInstrumentation().targetContext
musicRepository.loadMusicFiles(context.contentResolver)
val musicFiles = musicRepository.musicFiles
val viewModel = FragmentMusicListViewModel(SavedStateHandle(), musicRepository)
viewModel.onCleared()
assert(!musicFiles.hasObservers())
}
In addition, I have debugged the test on the last line, and musicFile's attributes show the observer is still attached. Attributes mActiveCount and mObservers show the observer is still attached,
How do I actually remove the observer?
CodePudding user response:
LiveData takes an argument of type Observer<T>
in observeForever and removeObserver. Observer is what Kotlin considers a functional interface written in Java in the androidx.lifecycle library.
What you are passing in is of type (List<MusicFile>) -> Unit
.
This is a high order function and is not the same type as Observer<List<MusicFile>>
. They are functionally similar in that they both have one parameter of type List<MusicFile>
and both return Unit
, so what Kotlin does for the better or for the worse (the worse in this case) is it will "cast" the type for you.
When Kotlin "casts" from high-order function to functional interface it is creating a new object. This happens every single time in your code when either observeForever or removeObserver are called. That's why removeObserver isn't working, because you're actually not passing in the same object despite how the code looks. I've written about this before.
In short, you can fix your problem by changing the type of observer to Observer:
private val observer: Observer<List<MusicFile>> =
Observer { musicFiles: List<MusicFile> ->
// _uiState.postValue(FragmentMusicListState(musicFiles))
}