Using Observables to Render Responsive Lists: An RxJava Case Study - Part 1 of 3

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.

If you are new to RxJava, I recommend starting here. You can also check out a book Angus Huang and I wrote if you would like a more comprehensive learning resource on RxJava.

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.

Dashboard View

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.

Non-Reactive Parts

DashboardFriendViewModel

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
data class DashboardFriendViewModel(
   val emoji: String,
   val title: String,
   val subtitle: String,
   val hasUnread: Boolean
) {
   companion object Factory {
       @JvmStatic fun create(
           friend: Friend,
           message: Message
       ) : DashboardFriendViewModel {
           // Factory logic goes here...
       }
   }
}

DashboardFriendViewHolder

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
class DashboardFriendViewHolder(
   parent: ViewGroup
) : RecyclerView.ViewHolder(
    LayoutInflater.from(parent.context).inflate(
        R.layout.item_dashboard_friend, parent, false
    )
) {
 
   var viewModel: DashboardFriendViewModel? = null
       set(value) {
           field = value
           value?.let {
               textViewEmoji.text = it.emoji
               textViewTitle.text = it.title
               textViewSubtitle.text = it.subtitle
               imageViewUnread.visibility = if (hasUnread) View.VISIBLE else View.GONE
           }
       }
}

DashboardAdapter

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
class DashboardAdapter : 
    RecyclerView.Adapter<DashboardFriendViewHolder>() {
    var friendViewModels: List<DashboardFriendViewModel> = listOf()
        set(value) {
            field = value
            notifyDataSetChanged()
        }
    override fun onBindViewHolder(
        holder: DashboardFriendViewHolder, 
        position: Int
    ) {
        holder.viewModel = friendViewModels[position]
    }
    override fun getItemCount(): Int = friendViewModels.size
    override fun onCreateViewHolder(
        parent: ViewGroup, 
        viewType: Int
    ): DashboardFriendViewHolder {
        return DashboardFriendViewHolder(parent)
    }
}

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.

Reactive Parts

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.

DAO

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
class FriendDao {
    fun getAllFriends(): Observable<List<Friend>> {
        // ...
    }
    fun getFriend(userId: String): Observable<Friend> {
        // ...
    }
    
    // more methods here...
}

class MessageDao {
 
    fun getAllMessagesFrom(userId: String): Observable<List<Message>> {
        // ...
    }
    fun getRecentMessageWith(userId: String): Observable<Message> {   
        // ...
    }
    // more methods here...
}

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
friendDao.getAllFriends()
    .switchMap { friends ->
        val observables = friends.map { friend ->
            messageDao.getRecentMessageWith(friend.userId)
                .map { DashboardFriendViewModel.create(friend, it) }
        }
        Observable.combineLatest(observables) {
            Arrays.copyOf(
                it, 
                it.size,
                Array<DashboardFriendViewModel>::class.java
            ).toList()
        }
    }.observeOn(AndroidSchedulers.mainThread())
     .subscribe { adapter.friendViewModels = it }

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().

Summary

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.

Announcing New Book: Reactive Programming on Android with RxJava

I’m very pleased to announce that a book that Angus Huang and I started writing a few months back, “Reactive Programming on Android with RxJava”, is now published and available on LeanPub! 🎉

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.

Reactive Modelling on Android

This post was originally published on Toptal.

Concurrency and asynchronicity are inherent to mobile programming.

Dealing with concurrency through imperative-style programming, which is what programming on Android generally involves, can be the cause of many problems. Using Reactive Programming with RxJava, you can avoid potential concurrency problems by providing a cleaner and less error-prone solution.

Aside from simplifying concurrent, asynchronous tasks, RxJava also provides the ability to perform functional style operations that transform, combine, and aggregate emissions from an Observable until we achieve our desired result.

By combining RxJava’s reactive paradigm and functional style operations, we can model a wide range of concurrency constructs in a reactive way, even in Android’s non-reactive world. In this article, you will learn how you can do exactly that. You will also learn how to adopt RxJava into an existing project incrementally.

If you are new to RxJava, I recommend reading the post here which talks about some of the fundamentals of RxJava.

Bridging Non-Reactive into the Reactive World

One of the challenges of adding RxJava as one of the libraries to your project is that it fundamentally changes the way that you reason about your code.

RxJava requires you to think about data as being pushed rather than being pulled. While the concept itself is simple, changing a full codebase that is based on a pull paradigm can be a bit daunting. Although consistency is always ideal, you might not always have the privilege to make this transition throughout your entire code base all at once, so more of an incremental approach may be needed.

Consider the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * @return a list of users with blogs
 */
public List<User> getUsersWithBlogs() {
   final List<User> allUsers = UserCache.getAllUsers();
   final List<User> usersWithBlogs = new ArrayList<>();
   for (User user : allUsers) {
       if (user.blog != null && !user.blog.isEmpty()) {
           usersWithBlogs.add(user);
       }
   }
   Collections.sort(usersWithBlogs, (user1, user2) -> user1.name.compareTo(user2.name));
   return usersWithBlogs;
}

This function gets a list of User objects from the cache, filters each one based on whether or not the user has a blog, sorts them by the user’s name, and finally returns them to the caller. Looking at this snippet, we notice that many of these operations can take advantage of RxJava operators; e.g., filter() and sorted().

Rewriting this snippet then gives us:

1
2
3
4
5
6
7
8
/**
 * @return a list of users with blogs
 */
public Observable<User> getUsersWithBlogs() {
   return Observable.fromIterable(UserCache.getAllUsers())
                    .filter(user -> user.blog != null && !user.blog.isEmpty())
                    .sorted((user1, user2) -> user1.name.compareTo(user2.name));
}

The first line of the function converts the List<User> returned by UserCache.getAllUsers() to an Observable<User> via fromIterable(). This is the first step into making our code reactive. Now that we are operating on an Observable, this enables us to perform any Observable operator in the RxJava toolkit – filter() and sorted() in this case.

There are a few other points to note about this change.

First, the method signature is no longer the same. This may not be a huge deal if this method call is only used in a few places and it’s easy to propagate the changes up to other areas of the stack; however, if it breaks clients relying on this method, that is problematic and the method signature should be reverted.

Second, RxJava is designed with laziness in mind. That is, no long operations should be performed when there are no subscribers to the Observable. With this modification, that assumption is no longer true since UserCache.getAllUsers() is invoked even before there are any subscribers.

Leaving the Reactive World

To address the first issue from our change, we can make use of any of the blocking operators available to an Observable such as blockingFirst() and blockingNext(). Essentially, both of these operators will block until an item is emitted downstream: blockingFirst() will return the first element emitted and finish, whereas blockingNext() will return an Iterable which allows you to perform a for-each loop on the underlying data (each iteration through the loop will block).

A side-effect of using a blocking operation that is important to be aware of, though, is that exceptions are thrown on the calling thread rather than being passed to an observer’s onError() method.

Using a blocking operator to change the method signature back to a List<User>, our snippet would now look like this:

1
2
3
4
5
6
7
8
9
10
/**
 * @return a list of users with blogs
 */
public List<User> getUsersWithBlogs() {
   return Observable.fromIterable(UserCache.getAllUsers())
           .filter(user -> user.blog != null && !user.blog.isEmpty())
           .sorted((user1, user2) -> user1.name.compareTo(user2.name))
           .toList()
           .blockingGet();
}

Before calling a blocking operator (i.e. blockingGet()) we first need to chain the aggregate operator toList() so that the stream is modified from an Observable<User> to a Single<List<User>> (a Single is a special type of Observable that only emits a single value in onSuccess(), or an error via onError()).

Afterwards, we can call the blocking operator blockingGet() which unwraps the Single and returns a List<User>.

Although RxJava supports this, as much as possible this should be avoided as this is not idiomatic reactive programming. When absolutely necessary though, blocking operators are a nice initial way of stepping out of the reactive world.

The Lazy Approach

As mentioned earlier, RxJava was designed with laziness in mind. That is, long-running operations should be delayed as long as possible (i.e., until a subscribe is invoked on an Observable). To make our solution lazy, we make use of the defer() operator.

defer() takes in an ObservableSource factory which creates an Observable for each new observer that subscribes. In our case, we want to return Observable.fromIterable(UserCache.getAllUser()) whenever an observer subscribes.

1
2
3
4
5
6
7
8
/**
 * @return a list of users with blogs
 */
public Observable<User> getUsersWithBlogs() {
   return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers()))
                    .filter(user -> user.blog != null && !user.blog.isEmpty())
                    .sorted((user1, user2) -> user1.name.compareTo(user2.name));
}

Now that the long running operation is wrapped in a defer(), we have full control as to what thread this should run in simply by specifying the appropriate Scheduler in subscribeOn(). With this change, our code is fully reactive and subscription should only occur at the moment the data is needed.

1
2
3
4
5
6
7
8
9
/**
 * @return a list of users with blogs
 */
public Observable<User> getUsersWithBlogs() {
   return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers()))
                    .filter(user -> user.blog != null && !user.blog.isEmpty())
                    .sorted((user1, user2) -> user1.name.compareTo(user2.name))
                    .subscribeOn(Schedulers.io());
}

Another quite useful operator for deferring computation is the fromCallable() method. Unlike defer(), which expects an Observable to be returned in the lambda function and in turn “flattens” the returned Observable, fromCallable() will invoke the lambda and return the value downstream.

1
2
3
4
5
6
7
8
9
/**
 * @return a list of users with blogs
 */
public Observable<User> getUsersWithBlogs() {
   final Observable<List<User>> usersObservable = Observable.fromCallable(() -> UserCache.getAllUsers());
   final Observable<User> userObservable = usersObservable.flatMap(users -> Observable.fromIterable(users));
   return userObservable.filter(user -> user.blog != null && !user.blog.isEmpty())
                        .sorted((user1, user2) -> user1.name.compareTo(user2.name));
}

Single using fromCallable() on a list would now return an Observable<List<User>>, we need to flatten this list using flatMap().

Reactive-everything

From the previous examples, we have seen that we can wrap any object in an Observable and jump between non-reactive and reactive states using blocking operations and defer()/fromCallable(). Using these constructs, we can start converting areas of an Android app to be reactive.

Long Operations

A good place to initially think of using RxJava is whenever you have a process that takes a while to perform, such as network calls (check out previous post for examples), disk reads and writes, etc. The following example illustrates a simple function that will write text to the file system:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Writes {@code text} to the file system.
 *
 * @param context a Context
 * @param filename the name of the file
 * @param text the text to write
 * @return true if the text was successfully written, otherwise, false
 */
public boolean writeTextToFile(Context context, String filename, String text) {
   FileOutputStream outputStream;
   try {
       outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
       outputStream.write(text.getBytes());
       outputStream.close();
       return true;
   } catch (Exception e) {
       e.printStackTrace();
       return false;
   }
}

When calling this function, we need to make sure that it is done on a separate thread since this operation is blocking. Imposing such a restriction on the caller complicates things for the developer which increases the likelihood of bugs and can potentially slow down development.

Adding a comment to the function will of course help avoid errors by the caller, but that is still far from bulletproof.

Using RxJava, however, we can easily wrap this into an Observable and specify the Scheduler that it should run on. This way, the caller doesn’t need to be concerned at all with invoking the function in a separate thread; the function will take care of this itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Writes {@code text} to the filesystem.
 *
 * @param context a Context
 * @param filename the name of the file
 * @param text the text to write
 * @return An Observable emitting a boolean indicating whether or not the text was successfully written.
 */
public Observable<Boolean> writeTextToFile(Context context, String filename, String text) {
   return Observable.fromCallable(() -> {
       FileOutputStream outputStream;
       outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
       outputStream.write(text.getBytes());
       outputStream.close();
       return true;
   }).subscribeOn(Schedulers.io());
}

Using fromCallable(), writing the text to file is deferred up until subscription time.

Since exceptions are first-class objects in RxJava, one other benefit of our change is that the we no longer need to wrap the operation in a try/catch block. The exception will simply be propagated downstream rather than being swallowed. This allows the caller to handle the exception a he/she sees fit (e.g. show an error to the user depending on what exception was thrown, etc.).

One other optimization we can perform is to return a Completable rather than an Observable. A Completable is essentially a special type of Observable — similar to a Single — that simply indicates if a computation succeeded, via onComplete(), or failed, via onError(). Returning a Completable seems to make more sense in this case since it seems silly to return a single true in an Observable stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Writes {@code text} to the filesystem.
 *
 * @param context a context
 * @param filename the name of the file
 * @param text the text to write
 * @return A Completable
 */
public Completable writeTextToFile(Context context, String filename, String text) {
   return Completable.fromAction(() -> {
       FileOutputStream outputStream;
       outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
       outputStream.write(text.getBytes());
       outputStream.close();
   }).subscribeOn(Schedulers.io());
}

To complete the operation, we use the fromAction() operation of a Completable since the return value is no longer of interest to us. If needed, like an Observable, a Completable also supports the fromCallable() and defer() functions.

Replacing Callbacks

So far, all the examples that we’ve looked at emit either one value (i.e., can be modelled as a Single), or tell us if an operation succeeded or failed (i.e., can be modelled as a Completable).

How might we convert areas in our app, though, that receive continuous updates or events (such as location updates, view click events, sensor events, and so on)?

We will look at two ways to do this, using create() and using Subjects.

create() allows us to explicitly invoke an observer’s onNext()|onComplete()|onError() method as we receive updates from our data source. To use create(), we pass in an ObservableOnSubscribe which receives an ObservableEmitter whenever an observer subscribes. Using the received emitter, we can then perform all the necessary set-up calls to start receiving updates and then invoke the appropriate Emitter event.

In the case of location updates, we can register to receive updates in this place and emit location updates as received.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LocationManager {

   /**
    * Call to receive device location updates.
    * @return An Observable emitting location updates
    */
   public Observable<Location> observeLocation() {
       return Observable.create(emitter -> {
           // Make sure that the following conditions apply and if not, call the emitter's onError() method
           // (1) googleApiClient is connected
           // (2) location permission is granted
           final LocationRequest locationRequest = new LocationRequest();
           locationRequest.setInterval(1000);
           locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

           LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() {
               @Override public void onLocationChanged(Location location) {
                   if (!emitter.isDisposed()) {
                       emitter.onNext(location);
                   }
               }
           });
       });
   }
}

The function inside the create() call requests location updates and passes in a callback that gets invoked when the device’s location changes. As we can see here, we essentially replace the callback-style interface and instead emit the received location in the created Observable stream (for the sake of educational purposes, I skipped some of the details with constructing a location request, if you want to delve deeper into the details you can read it here).

One other thing to note about create() is that, whenever subscribe() is called, a new emitter is provided. In other words, create() returns a cold Observable. This means that, in the function above, we would potentially be requesting location updates multiple times, which is not what we want.

To work around this, we want to change the the function to return a hot Observable with the help of Subjects.

Enter Subjects

A Subject extends an Observable and implements Observer at the same time. This is particularly useful whenever we want to emit or cast the same event to multiple subscribers at the same time. Implementation-wise, we would want to expose the Subject as an Observable to clients, while keeping it as a Subject for the provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LocationManager {

   private Subject<Location> locationSubject = PublishSubject.create();
   
   /**
    * Invoke this method when this LocationManager should start listening to location updates.
    */
   public void connect() {
       final LocationRequest locationRequest = new LocationRequest();
       locationRequest.setInterval(1000);
       locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

       LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() {
           @Override public void onLocationChanged(Location location) {
               locationSubject.onNext(location);
           }
       });
   }
   
   /**
    * Call to receive device location updates.
    * @return An Observable emitting location updates
    */
   public Observable<Location> observeLocation() {
       return locationSubject;
   }
}

In this new implementation, the subtype PublishSubject is used which emits events as they arrive starting from the time of subscription. Accordingly, if a subscription is performed at a point when location updates have already been emitted, past emissions will not be received by the observer, only subsequent ones. If this behavior is not desired, there are a couple of other Subject subtypes in the RxJava toolkit that can be used.

In addition, we also created a separate connect() function which starts the request to receive location updates. The observeLocation() can still do the connect() call, but we refactored it out of the function for clarity/simplicity.

Summary

We’ve looked at a number of mechanisms and techniques:

  • defer() and its variants to delay execution of a computation until subscription
  • cold Observables generated through create()
  • hot Observables using Subjects
  • blockingX() operations when we want to leave the reactive world

Hopefully, the examples provided in this article inspired some ideas regarding different areas in your app that can be converted to be reactive. We’ve covered a lot and if you have any questions, suggestions, or if anything is not clear, feel free to leave a comment below!

If you are interested in learning more about RxJava, I am working on an in-depth book that explains how to view problems the reactive way using Android examples. If you’d like to receive updates on it, please subscribe here.