Demystifying Reactive Android Apps

Many of us in the Android community have been using RxJava to perform complex asynchronous tasks but have often fallen short of using it to create a truly reactive application.

In this post, we’ll walk through implementing a feature of an Android TODO app in a reactive manner, touching on some of the places where people typically diverge from a true reactive flow.

What is reactive programming?

Before we get started, let’s take a moment to outline what reactive programming is. At its core, reactive programming is a style of programming wherein data is represented as streams of information that a program can easily react to. Manifestations of reactive programming often rely heavily on the observer pattern; wherein some object has a list of observers that it notifies about changes in its internal state. For RxJava, the observers in question are the objects calling subscribe on an Observable.

Components outline

Before we dive into the code, it’s worth touching on the architecture the app uses. Most of the pieces won’t be anything new to the average Android developer, but the way they interact with each other may be.

There’s a ViewModel, which drives UI logic. It typically observes a Repository which makes network or database calls and handles business logic that is less directly related to the UI. And there is of course a View, usually represented by an Android Activity or Fragment.

Diving in

To demonstrate building a feature in a reactive manner, we’ll focus in on a theoretical feature of our TODO app that allows a user to view their “top” TODO for the day.

You’ll need an object to model a todo:

data class Todo(val title: String, val dueDate: Date, val isComplete: Boolean)

You’ll need some data layer component that can fetch the top todo and save an updated todo:

interface TopTodoService {
  fun topTodo(): Single<Todo>
  fun saveTodo(todo: Todo): Completable
}

The data layer could be saving to a database or fetching from a network; it’s not particularly important. All that matters is that it implements the above interface.

At this point it’s worth taking a minute to recognize that just because the TopTodoService exposes RxJava types for its methods does not mean it’s exposing a reactive API. A reactive API should allow a consumer to observe or listen for some change whenever a change is made to the internal state of whatever the API is exposing.

Here’s a (contrived) example of a non reactive repository that wraps the TopTodoService:

class NonReactiveRepository(private val service: TopTodoService) {
  fun fetchTopTodo(): Single<Todo> {
    return service.topTodo()
  }

  fun saveTopTodo(todo: Todo): Completable {
    return service.saveTodo(todo)
  }
}

Calling fetchTopTodo on this repository should theoretically give you the top TODO for the day. However, if you’re calling fetchTopTodo from your ViewModel you’ll need to make sure to re-query it whenever the top TODO is updated. That means you need to keep track of where in your code you’re updating that TODO, and you need to make sure you’re always remembering to update the UI whenever those changes are made.

In a truly reactive system, you’re observing streams of data, so you know you’re always operating on the most up to date information the system can provide.

You know this isn’t a truly reactive API because fetchTopTodo returns a Single. You can’t model a stream of information with a Single, so that’s a good hint that you’ll need to do a bit more work to expose a genuinely reactive API.

Creating a reactive API

Instead, here’s a truly reactive wrapper around TopTodoService:

class TopTodoRepository(private val service: TopTodoService) {
  private val disposables = CompositeDisposable()
  private val topTodoSubject = BehaviorSubject.create<Todo>()
  val topTodoObservable = topTodoSubject.hide()

  init {
    fetchTopTodo()
  }

  private fun fetchTopTodo() {
    service
      .topTodo()
      .subscribeOn(Schedulers.io())
      .subscribe(topTodoSubject::onNext)
      .addTo(disposables)
  }

  fun completeTodo(todo: Todo) {
    val completedTodo = todo.copy(isComplete = true)
    service.saveTodo(completedTodo)
      .subscribeOn(Schedulers.io())
      .subscribe { topTodoSubject.onNext(completedTodo) }
      .addTo(disposables)
  }
}

The above repository works such that whenever completeTodo is called, a new value is piped through the topTodoSubject. It will also immediately send a new Todo through topTodoSubject as soon as TopTodoRepository is created. As a result, any class that uses the TopTodoRepository can subscribe to the topTodoObservable and will receive a steady stream of whatever the top Todo is, even as the top Todo changes!

By exposing all of the repositories state in a single observable, your ViewModel becomes entirely a function of the TopTodoRepository‘s observable:

class TopTodoViewModel(val repo: TopTodoRepository): ViewModel() {
  private val disposables = CompositeDisposable()
  val dueDateLiveData = MutableLiveData<Date>()
  val titleLiveData = MutableLiveData<String>()
  val isCompleteLiveData = MutableLiveData<Boolean>()

  init {
    val todoObservable = repo.topTodoObservable
      .subscribeOn(Schedulers.io())
      .share()

    todoObservable
      .map { it.copy(dueDate = Date()) }
      .map { it.dueDate }
      .subscribe(dueDateLiveData::postValue)
      .addTo(disposables)

    todoObservable
      .map { it.title }
      .subscribe(titleLiveData::postValue)
      .addTo(disposables)

    todoObservable
      .map { it.isComplete }
      .subscribe(isCompleteLiveData::postValue)
      .addTo(disposables)
  }
}

Critically, the above will keep the UI up to date as the internal state of the repo changes.

Responding to user actions

Consuming information is one thing, but the above code doesn’t deal with changing the state of the world at all. Imagine we have some view on the screen that when clicked should mark a Todo as completed.

Ultimately it’s the View that will invoke this action, so we need some way for the View to communicate that action with the ViewModel.

At first pass the answer seems obvious. Simply add a public function to the ViewModel that calls into the repo:

fun todoCompleted() {
  repo.completeTodo(???)
}

However, at this point you don’t know what the Todo is that should be updated.

You have a few options:

  1. You could send the initial Todo over to the View when it’s first loaded so the View can then bubble it back up to the ViewModel when the completion action is taken.
  2. You can subscribe to the todoObservable in the ViewModel and save the currently displayed Todo and use that when todoCompleted is called
  3. You could flip the table around and expose whatever click listener calls todoCompleted as an observable and pass that into the view model.

The first option violates the single responsibility principle. Ideally the View has no insight into the business objects.

The second option, keeping a local copy of the Todo, solves the single responsibility violation introduced in the first option, but it has the downside of introducing mutable state to your view model. You’d have to make sure that your local copy of the Todo object is always up to date and is never updated incorrectly.

The third option, treating a view like an observable, is absolutely a viable approach to take. However, it too comes with several downsides:

  • If you’re going to make an observable out of a click listener, you need to be aware of the threading limitations that come with interacting with views. Specifically, you need to make sure that the piece adding the click listener does so on the main thread.
  • Since most Android views can only have one associated click listener, you’ll only be able to subscribe to the observable once before you start overwriting other subscriptions. You can avoid this by using the share operator, but it’s a hidden trap to fall into.

There is also a surprise fourth option: stop treating click listeners any different than the other dynamic properties you’re updating!

Just like you’d send a String to be displayed in a TextView via a LiveData stream, you can send a ClickListener function to the View via a LiveData object.

val finishedTaskClickListenerLiveData = MutableLiveData<() -> Unit>()

todoObservable
  .subscribe { todo ->
    finishedTaskClickListenerLiveData.postValue {
      repo.completeTodo(todo)
    }
  }
  .addTo(disposables)

Now the finishedTaskClickListenerLiveData will deliver a function that calls repo.completeTodo with the correct Todo. There’s no special logic to handle click listeners; it’s just another mutable property of a view that you deliver via LiveData.

The View

Building out the View is now straightforward. All it involves is subscribing to the LiveDatas that the ViewModel exposes:

class TopTodoActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val repo = TopTodoRepository(FakeService())
    val viewModel = TopTodoViewModel(repo)

    viewModel.dueDateLiveData.observe(this, Observer {
      dueDate.text = it?.toString()
    })
    viewModel.titleLiveData.observe(this, Observer {
      titleText.text = it
    })
    viewModel.isCompleteLiveData.observe(this, Observer {
      isFinished.text = if (it!!) { "Done!" } else { "Not Done!" }
    })
    viewModel.finishedTaskClickListenerLiveData.observe(this, Observer { click ->
      finishedTaskButton.setOnClickListener { click?.invoke() }
    })
  }
}

The only novel piece of code here is the last observing block:

viewModel.finishedTaskClickListenerLiveData.observe(this, Observer { click ->
  finishedTaskButton.setOnClickListener { click?.invoke() }
})

The finishedTaskClickListenerLiveData delivers a LiveData<() -> Unit>, which the View then observes. Since setOnClickListener expects a function of type (View) -> Unit, you’ll have to create a new OnClickListener and manually invoke the delivered function. This can be easily cleaned up with a simple extension method, but that’s out of scope for this blog post.

Wrapping up

Utilizing a reactive architecture can simplify your application’s logic and guarantee that your users are seeing the most up to date information. But it take’s some effort to ensure that you’re getting the most out of your app’s RxJava and LiveData usage. In general, make sure to:

  • Verify that any data that can change is represented as an Observable<Data> rather than a Single.
  • Aim to have your business logic components expose observables that represent their state rather than passing those observables back up through to the view or viewmodel.
  • Try and construct your view models such that they could theoretically be written entirely in the init block. That’s not to say that you should just put everything in the init block, but if you’re finding that you don’t have the information you need to model the inputs and outputs of your view model in the init block you may be missing a reactive piece in your system.