This post was originally posted on Medium.
This is the 1st part of a 3 part series about how RxJava is used in Pre, a location-based app for checking in and chatting with your best friends. In this first post, I will go over how we used Observables to compose a complex view that displays a list of items, specifically, the dashboard view.
Note that all of the code samples in this series are written in Kotlin. In addition to RxJava, we use RxKotlin which is a lightweight library that provides convenient extension functions to RxJava.
The dashboard view is the first view presented to the user upon logging in. It contains 2 sections: (1) a section displaying a list of recent check-ins from your friends, and (2) a section displaying a list of all your friends. The latter is displayed by querying the data layer for all of your friends, followed by querying the most recent message in the conversation thread for each of your friends. The state of the message will then determine if an unread indicator, or if a relative timestamp of when that message was sent, should be displayed.
Both the Friend and Message models are persisted in a local SQLite database. To prevent jankiness while scrolling, retrieving these models should be done before rendering the list. More specifically, we want to make sure that we have all of the Friend objects and the corresponding latest Message in memory before setting the list of friends to the Adapter of the RecyclerView.
Before we dive into Observables and how RxJava fits into the picture, let’s look at a few classes that compose the dashboard view. Note that even though some of the classes here have been shortened for brevity, the underlying concepts should be the same.
On the dashboard, the DashboardFriendViewModel class is in charge of binding the Friend and Message models to a single friend view. True to the ViewModel design pattern, this class provides us with the benefit of abstracting away model details from the view layer. So if our model changes, we would only need to update the ViewModel’s code (i.e. DashboardFriendViewModel), and not the view’s code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Using a DashboardFriendViewModel, a DashboardFriendViewHolder (a subclass of RecyclerView.ViewHolder), can simply update the views it holds by getting the fields on the DashboardFriendViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
The backing adapter for the dashboard RecyclerView, DashboardAdapter, holds a list of DashboardFriendViewModel objects and binds each object to a DashboardFriendViewHolder (note that check-ins are excluded in the code sample below).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
With these classes in mind, it should be clear that the role of the Activity is to provide the DashboardAdapter a list of DashboardFriendViewModel objects to construct the list of friends in the dashboard view. In the next section, we will look at the reactive elements that come into play to accomplish this.
Each model that is backed by a SQLite table has a corresponding data access object, or DAO for short, that is in charge of performing CRUD (create, read, update, and delete) operations. The DAO provides the application layer with a higher level abstraction when it needs to access models so that it doesn’t need to know how to perform complex SQLite queries.
DAOs in Pre are also designed to be reactive; that is, DAOs can provide data packaged as an Observable so that the requestor can continue to receive updates as the underlying model changes. The details of how these Observables are constructed will be covered in the 2nd part of this series. For now, assume that we have the following methods provided by FriendDao and MessageDao.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
It is important to keep in mind that these methods return Observables that report changes to subscribers as the underlying data also changes. For example, if we subscribe to FriendDao#getAllFriends() and, at a later point, a new Friend is added, the observer would receive an updated list of friends via .onNext() once the new friend is persisted in the database. This is really powerful as the mechanism for requesting initial data and for receiving updates will all be in the same place — the observer.
In addition, all methods that return Observables in Pre’s DAOs run off the main thread. It is up to the subscriber to ultimately hop back to the main thread, via .observeOn(), when necessary (e.g. when interacting with the view layer).
Putting It All Together 🏗
Using FriendDao and MessageDao that supply us with Observables of Friend and Message objects, respectively, we can now construct the list of DashboardFriendViewModel objects and display our list of friends on the dashboard.
The lines of code that accomplish this are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
There’s quite a lot going on here so let’s look at it step-by-step:
First, we retrieve all friends by calling the .getAllFriends() method on a FriendDao object. As mentioned earlier, this Observable will first emit the list of friends as well as any changes to the user’s friends (i.e. a list of friends will be emitted as new friends are added, or as existing friends are removed/updated)
Next, we apply a .switchMap() operator which maps the stream from a list of Friend objects to a list of DashboardFriendViewModel objects, the type we are ultimately interested in. We are using .switchMap() here, instead of .flatMap() or .concatMap(), so that only the most recent Observable emission from .switchMap() is observed and all other previous emissions will be considered stale. You can read about the difference between the operators on the ReactiveX wiki.
Within the .switchMap() operator, we then convert friends into a list of Observable
by iterating through each friend and getting the most recent message with them followed by mapping that to a DashboardFriendViewModel.
We then combine the emissions of the list of Observable
using the .combineLatest() operator, the result of which is then combined into a list of DashboardFriendViewModel objects which is propagated down to the observer. This operator allows downstream operators and observers to receive updates whenever there is a new message from a friend.
After the .switchMap() operator, we then chain an .observeOn() operator and make sure that the observer receives events on Android’s main thread.
Finally, we subscribe to the Observable after applying .switchMap() and set the DashboardAdapter’s friendViewModels to the received emissions. Here, we are replacing the backing data set for the DashboardAdapter, which in turn invokes notifyDataSetChanged(). This can also be optimized by finding the items with changes and selectively applying notifiyItemChanged() or notifyItemRangeChanged().
The solution above is brief and accomplishes our goal of displaying the dashboard view as well as keeping it up-to-date whenever there is new data. Having a reactive data layer at Pre enables this. By no means is this the only way to compose complex UI. However, I hope this post convinced you that working with Observables makes dealing with data that can come from different sources as well as coordinating concurrency an easier task.
Pre is now available in the Google Play Store 🎉 Get it here.
Subscribe at the bottom of this site if you want to be notified when the next post in the series goes out.
What is RxJava?
RxJava—the Java implementation of ReactiveX—was open sourced and introduced to the developer community by Netflix back in 2013. At Netflix, RxJava had arisen as a need to solve scaling issues created by their previous one-size-fits-all API.
The promise of reactive programming was that it would allow their teams to seamlessly compose complex asynchronous behavior into an easy-to-use API. Using these APIs, their client teams can then create custom end-points to optimize for the growing number of devices that Netflix supported without having to deal with the intricacies of server-side concurrent programming.
In a word, RxJava was supposed to simplify writing concurrent code.
Turns out, RxJava did fulfill its promise and is now the backbone of many Netflix back-end services.
Outside of Netflix, RxJava has been adopted in other communities, including the Android community, as reactive programming can also help with developing mobile apps. As of today, RxJava is the go-to library for enabling reactive programming on Android. Surely, the number of stars it has on Github should be a strong signal.
Why Write a Book?
We believe that reactive programing is shaping the way Android apps are being built. This is even evident in the direction Google is going with its new reactive-inspired Android Architecture Components announced recently at Google IO ‘17. With that said, we think it’s important for Android developers to familiarize themselves with the reactive programming model.
This book is a collection of our knowledge on the subject taken from different sources around the web (i.e. blog posts, books, wikis, etc.). Our hope is that this book serves as a solid foundation for Android developers who are new to RxJava and want to start integrating it into their apps.
If you are interested in learning more, you can purchase or download a sample of the book here.
Got any questions? Leave us a comment below.
Writing apps in Kotlin? Stay tuned for our next book.