A Pitfall in LiveData and Android Architecture Components

As written about previously, Rocket has been developing new Android apps using Android Architecture Components. Overall, it has been a good experience with clear separation of concerns, a prescribed, defined architecture, and some useful classes along with it.

While is has been useful, one pitfall that we ran into was in using LiveData.

Many of the examples you'll find typically go like this:

myViewModel.myLiveData
    .observe(this /*Activity or Fragment*/, Observer { myLiveData ->
  // do something with myLiveData
})

That's great in many cases. What about when you need to observe LiveData outside of the lifecycle that comes with Activities or Fragments?

One might think you can do the following:

val myLiveData = MutableLiveData<SomeData?>()
myLiveData.observeForever(object : Observer<SomeData?> {
  override fun onChanged(someData: SomeData?) {
    // do something with someData
    myLiveData.removeObserver(this)
  }
})

However, that will occasionally lead to exceptions. We've seen ConcurrentModificationExceptions, ArrayIndexOutOfBoundsExceptions, NullPointerExceptions, IllegalArgumentExceptions, and IndexOutOfBoundsExceptions in our development. Exceptions can happen when adding the observer or removing it. Even if you catch these, eventually observers will stop working entirely and while your app may look like it's responding, it becomes inoperable. You could use observeForever by itself, but that isn't the intended use-case here.

Similarly, you might think you can do this:

myViewModel.myLiveData
    .observe(this /*MyActivity*/, Observer { myLiveData ->
  // do something with myLiveData
  myViewModel.myLiveData.removeObserver(this@MyActivity)
})

However, eventually you'll still run into those exceptions.

As a result, we've ended up passing our lifecycle references in situations where we need to observe changes happening outside of the actual lifecycle object. That is clearly less than ideal. Since one of the advantages of using Room is that it returns LiveData, we're somewhat backed into the corner on that.

Going forward, we'll have to take a hard look at just how we use Android Architecture Components. A mix of using ViewModels and LiveData in Activities and Fragments while using RxJava for interacting between ViewModels and Repositories or other data layers seems like it could be the optimal solution, even if it means you're using different frameworks and paradigms within the same app.

Software development is a learning process defined by adapting to change. Clearly, Android development is no different.