Clearer RxJava intentions with Single and Completable

Valentin Hinov
AndroidPub
Published in
5 min readMar 9, 2017

--

In almost all RxJava example code and tutorials there is one class that reigns supreme — the Observable. It is the object which makes the whole magic of reactive programming possible. It’s simple — you only have to track 3 events — onNext, onError, and onCompleted and you can apply all of the hundreds of possible operators to make it do your bidding. Why would you need anything else, right?

Have you considered whether you actually need to know about those 3 events every time? Hint — in most cases, you don’t. The documentation over at ReactiveX goes on about continuous streams of events so much that we often forget we usually only care about observing just one event that happened or just observing that something completed or failed .

In such cases you should consider using two wonderful RxJava constructs called Single<T> and Completable. Before we talk about them, let’s look at an example of where they might apply.

Before you look at the code I want to specify all of my examples will be using RxJava 2.x, not the 1.x version. If you haven’t yet upgraded your RxJava projects to the latest 2.x version, I highly encourage you to. I’m also using Java 8 lambdas to make code easier to read.

Single

One of the most common use cases for RxJava in Android is for doing network calls. If you’re an Rx user on Android you’re probably using Retrofit as your HTTP client. Say you have a GET HTTP call which returns some data. When you use the RxJavaAdapter you can declare your API client like so:

public interface APIClient {

@GET("my/api/path")
Observable<MyData> getMyData();
}

This is perfectly fine and you can use it as is. The code that uses it can take a form like this:

apiClient.getMyData()
.subscribe(myData -> {
// handle data fetched successfully
}, throwable -> {
// handle error event
}, () -> {
// handle on complete event
});

When you think about it though — this really isn’t really a continuously streaming event is it? Your API call isn’t going to return your result twice or in multiple pieces. No, you make the call to fetch your data once and you’re only expecting a single result. We know that onComplete will follow as soon as onNext happens so why can’t we combine them?

To make your intentions in this case clearer, you can instead replace the Observable with a Single<MyData>. From the documentation on Single you can find out why it’s the perfect candidate: A Single is something like an Observable, but instead of emitting a series of values — anywhere from none at all to an infinite number — it always either emits one value or an error notification. Sounds ideal, right? So our API client now looks like this:

public interface APIClient {

@GET("my/api/path")
Single<MyData> getMyData();
}

Now, our above use case can be simplified to this:

apiClient.getMyData()
.subscribe(myData -> {
// handle data fetched successfully and API call completed
}, throwable -> {
// handle error event
});

The nice part about this is that even though you’re not using Observable anymore, Single implements almost all of the operators you’d often use on an Observable — map, flatMap , filter, zip — they’re all there — the only difference being they return or work with Singles instead of Observables. If you find you need to use an operator that Single does not support or you want to use an Observable representation of it you can use the handy toObservable operator to automagically convert a Single<T> to an Observable<T>.

apiClient.getMyData()
.toObservable()
// This is an Observable<MyData> now

Oh, and if for some reason you have an Observable that you know for sure behaves like a Single, you can convert it using the singleOrError operator.

Completable

Continuing with our Retrofit example, let’s consider another common API case — doing a PUT request to update an object. We want to change some properties on the MyData object and then send it to the server to update in its database. What most server API design does in this case is just respond with the updated object when successful. So your client code would be:

public interface APIClient {

@PUT("my/api/updatepath")
Observable<MyData> updateMyData(@Body MyData data);
}

And, similar to our previous example, you’d just use it like so

apiClient.updateMyData(myUpdatedData)
.subscribe(myData -> {
// handle data fetched successfully and API call completed
}, throwable -> {
// handle error event
}, () -> {
// handle completion - what we actually care about
});

You may say that in here we could again just use Single to cut down on our code and you’d be correct. In this case do we even need the MyData result, though? The server is returning it to us with good intentions, but it’s essentially just giving us back what we already sent it if it was successful. What we want is to just be notified that the update succeeded. Meaning, we just want the onComplete event.

Enter the Completable— an object that represents the exact intention we’re going for. Its description: Represents a computation without any value but only indication for completion or exception. By using Completable we agree to ignore the onNext event and only handle onComplete and onError. The API client then becomes:

public interface APIClient {

@PUT("my/api/updatepath")
Completable updateMyData(@Body MyData data);
}

This leads to our usage being:

apiClient.updateMyData(myUpdatedData)
.subscribe(() -> {
// handle completion
}, throwable -> {
// handle error
});

Completables by their nature are quite different from Observable and Single (due to not emitting items). Therefore you end up using a different set of operators if you want to chain actions. The most versatile one being andThen. In that operator you can pass any Observable, Single, Flowable, Maybe or other Completable and it’ll get executed when the original Completable completes. For example, if you wanted to perform some other operation which is a Single you’d do:

apiClient.updateMyData(myUpdatedData)
.andThen(performOtherOperation()) // a Single<OtherResult>
.subscribe(otherResult -> {
// handle otherResult
}, throwable -> {
// handle error
});

Unlike Single RxJava does not allow an Observable to be converted to a Completable directly, because there is no way to know that an Observable will ever complete. You can convert a Single to a Completable, though, since a Single guarantees that onComplete will get called. The operator to use in that case is toCompletable.

If you read my previous article on code tidiness with RxJava you’d know that simple and concise code like this makes me happy. Through this brief and simple introduction to Single and Completable hopefully you can see that these two constructs can help in the goal of clearer code. They better explain to the user what to expect.

--

--

Valentin Hinov
AndroidPub

I'm passionate about building applications which are both well designed and intelligently architected. Find me at https://valcanbuild.tech