Observer lambdas exceptions within Android LiveData

Seeing this kind of code is quite common nowadays. A fragment observe a livedata coming from a viewmodel and make something with it.

calendarViewModel.calendarRows.observe(viewLifecycleOwner) { rows -> rows.size }

However, if you instantiate a second time the fragment containing this code, you might encounter this lovely exception:

java.lang.IllegalArgumentException: Cannot add the same observer with different lifecycles

What happens is that the lambda becomes a singleton and is reused every time a new fragment is attached with each fragment not sharing the same lifecyle state.

If you reuse the same fragment via findFragmentByTag for example, you shouldn’t face the exception.

Let’s see how a few examples and how the code is decompiled in Java to check how to circumvent this exception.

The app contains a list of calendar events (it doesn’t really matter) in a Calendar Fragment with for example a Bottom Navigation view in which the first menu item reload the Calendar Fragment. The purpose is just to recreate multiple instances of a fragment.

We prepare the code to display a list of events with a recycler view, a liveData that observe a list of events and see if everything works with a minimalist Observer lambda that basically do nothing.

We have an Observer lambda that does nothing except computing the size of the rows returned by the liveData.

When we decompile the kotlin code in java, we get a static field called in place of the lambda that references a class which implements the Observer interface outside the CalendarFragment class.

The compiler will optimize the code and return the same instance every time. That’s the root cause of our problem.

The first time CalendarFragment is attached to an activity, the Observer is created. The second time CalendarFragment is replaced by another instance it will fetch the same instance of the Observer leading to the exception Cannot add the same observer with different lifecycles.

LiveData check that the observer is not already in the list of observers of the liveData with an another owner. The same instance of an Observer implementation shared across multiple owners of the liveData will throw this exception and crash the app.

So let’s try the same code without using lambdas but with an object that implements the Observer interface.

We pass a new instance each time we call the observe method on the liveData.

When we decompile the code in java, it explicitely pass a new instance of an Observer implementation. The class onChanged method doesn’t change much. It works and no exception is thrown.

Still, Kotlin provide lambdas to avoid boilerplate code, why shouldn’t we take advantage of it.

Let’s try another example with a lambda like in the first example.

Instead of using the result of the liveData, we compute the length of a string variable declared in the CalendarFragment class. Something outside the scope of the lambda.

When we decompile the code in java unlike in the first example, we pass a new instance of Observer instead of a static instance.

Upon looking at the implementation of Observer, we can see that using external variables, the CalendarFragment instance and the string is passed to the constructor, the compiler must create a new instance to pass these parameters. We avoid the same instance problem.

Usually, we pass the adapter or at least another variable from the fragment to the Observer lambda so we should be safe from this exception in most cases.

It can still be confusing to see the app crash depending on how you implement your Observer interface.

We saw that there are multiple ways to work around this behavior depending on your use case.

  • Using the same fragment instance
  • Not using a lambda
  • Passing an external variable inside the lambda to force a new instantiation