Single, Completable and Maybe in RxJava

Keywords: Apache network Fragment Retrofit


Maybe tomorrow.jpeg

Usually, if we want to use RxJava, the first thing we think about is using Observable. If we want to consider the situation of Backpress, we will use Flowable in the RxJava 2. x era. In addition to Observable and Flowable, there are three types of Observables in RxJava 2.x: Single, Completable, and Maybe.

type describe
Observable<T> It can emit 0 or n data and terminate with a success or error event.
Flowable<T> It can emit 0 or n data and terminate with a success or error event. Backpressure is supported to control the speed of data source transmission.
Single<T> Only single data or error events are emitted.
Completable It never emits data and only handles onComplete and onError events. It can be seen as Rx's unnable.
Maybe<T> Be able to transmit 0 or 1 data, either successfully or unsuccessfully. It's kind of like Optional.

As can be seen from the table above, only Flowable can support Backpressure among the five types of observers, and Flowable must be used if backpressure is required.

Single

From the source code of Single Emitter, we can see that Single has only onSuccess and onError events.

/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex;

import io.reactivex.annotations.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Cancellable;

/**
 * Abstraction over an RxJava {@link SingleObserver} that allows associating
 * a resource with it.
 * <p>
 * All methods are safe to call from multiple threads.
 * <p>
 * Calling onSuccess or onError multiple times has no effect.
 *
 * @param <T> the value type to emit
 */
public interface SingleEmitter<T> {

    /**
     * Signal a success value.
     * @param t the value, not null
     */
    void onSuccess(@NonNull T t);

    /**
     * Signal an exception.
     * @param t the exception, not null
     */
    void onError(@NonNull Throwable t);

    /**
     * Sets a Disposable on this emitter; any previous Disposable
     * or Cancellation will be unsubscribed/cancelled.
     * @param s the disposable, null is allowed
     */
    void setDisposable(@Nullable Disposable s);

    /**
     * Sets a Cancellable on this emitter; any previous Disposable
     * or Cancellation will be unsubscribed/cancelled.
     * @param c the cancellable resource, null is allowed
     */
    void setCancellable(@Nullable Cancellable c);

    /**
     * Returns true if the downstream cancelled the sequence.
     * @return true if the downstream cancelled the sequence
     */
    boolean isDisposed();
}

Where onSuccess() is used to transmit data (onNext() is used to transmit data in Observable/Flowable). And only one data can be transmitted, and no processing will be done even if the data is transmitted later.

Single Observer only has onSuccess, onError, and no onComplete. This is the biggest difference between Single and the other four observers.

Single.create(new SingleOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull SingleEmitter<String> e) throws Exception {

                e.onSuccess("test");
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(@NonNull String s) throws Exception {
                System.out.println(s);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {
                throwable.printStackTrace();
            }
        });

The above code, because there are two Consumer s in Observer, can be further simplified to

Single.create(new SingleOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull SingleEmitter<String> e) throws Exception {

                e.onSuccess("test");
            }
        }).subscribe(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) throws Exception {
                System.out.println(s);
            }
        });

Single can be converted to Observable, Flowable, Completable and Maybe by toXXX.

Completable

Completable does not emit any data after it is created. As you can see from the source code of Completable Emitter

/**
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
 * the License for the specific language governing permissions and limitations under the License.
 */

package io.reactivex;

import io.reactivex.annotations.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Cancellable;

/**
 * Abstraction over an RxJava {@link CompletableObserver} that allows associating
 * a resource with it.
 * <p>
 * All methods are safe to call from multiple threads.
 * <p>
 * Calling onComplete or onError multiple times has no effect.
 */
public interface CompletableEmitter {

    /**
     * Signal the completion.
     */
    void onComplete();

    /**
     * Signal an exception.
     * @param t the exception, not null
     */
    void onError(@NonNull Throwable t);

    /**
     * Sets a Disposable on this emitter; any previous Disposable
     * or Cancellation will be disposed/cancelled.
     * @param d the disposable, null is allowed
     */
    void setDisposable(@Nullable Disposable d);

    /**
     * Sets a Cancellable on this emitter; any previous Disposable
     * or Cancellation will be disposed/cancelled.
     * @param c the cancellable resource, null is allowed
     */
    void setCancellable(@Nullable Cancellable c);

    /**
     * Returns true if the downstream disposed the sequence.
     * @return true if the downstream disposed the sequence
     */
    boolean isDisposed();
}

Completable has only onComplete and onError events, while Completable has no operators such as map, flatMap, etc. It has much fewer operators than Observable/Flowable.

We can create a Completable by using the fromXXX operator. This is a Completable version of Hello World.

Completable.fromAction(new Action() {
            @Override
            public void run() throws Exception {

                System.out.println("Hello World");
            }
        }).subscribe();

Completable often combines the andThen operator

Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(@NonNull CompletableEmitter emitter) throws Exception {

                try {
                    TimeUnit.SECONDS.sleep(1);
                    emitter.onComplete();
                } catch (InterruptedException e) {
                    emitter.onError(e);
                }
            }
        }).andThen(Observable.range(1, 10))
        .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                System.out.println(integer);
            }
        });

After emitter.onComplete() is executed here, it indicates that Completable has been fully executed, followed by the operation in andThen.

The printing results are as follows:

1
2
3
4
5
6
7
8
9
10

In Completable, andThen has several overloaded methods that correspond to exactly five types of observers.

Completable       andThen(CompletableSource next)
<T> Maybe<T>      andThen(MaybeSource<T> next)
<T> Observable<T> andThen(ObservableSource<T> next)
<T> Flowable<T>   andThen(Publisher<T> next)
<T> Single<T>     andThen(SingleSource<T> next)

Completable can also be converted to Observable, Flowable, Single, and Maybe by toXXX.

In network operations, if an update is encountered, that is, a PUT operation in Restful architecture, it usually either returns the original object or only prompts the update to succeed. The following two interfaces use Retrofit, which is used to obtain the SMS authentication code and update user information, in which updating user information with PUT is more in line with Restful API.

    /**
     * Getting Short Message Verification Code
     * @param param
     * @return
     */
    @POST("v1/user-auth")
    Completable getVerificationCode(@Body VerificationCodeParam param);

    /**
     * User Information Update Interface
     * @param param
     * @return
     */
    @POST("v1/user-update")
    Completable update(@Body UpdateParam param);

This is roughly what you would write in the model class.

/**
 * Created by Tony Shen on 2017/7/24.
 */

public class VerificationCodeModel extends HttpResponse {

    /**
     * Get the authentication code
     * @param activity
     * @param param
     * @return
     */
    public Completable getVerificationCode(AppCompatActivity activity, VerificationCodeParam param) {

        return apiService
                .getVerificationCode(param)
                .compose(RxJavaUtils.<VerificationCodeModel>completableToMain())
                .compose(RxLifecycle.bind(activity).<VerificationCodeModel>toLifecycleTransformer());
    }
}

It is particularly important to note that getVerification Code returns Completable instead of Completable<T>.

The successful acquisition of the verification code gives the corresponding toast prompt, and the failure can be handled accordingly.

VerificationCodeModel model = new VerificationCodeModel();
model.getVerificationCode(RegisterActivity.this,param)
           .subscribe(new Action() {
                      @Override
                      public void run() throws Exception {
                              showShort(RegisterActivity.this,"Successful sending of authentication code");
                      }
            },new RxException<Throwable>(){
                      @Override
                     public void accept(@NonNull Throwable throwable) throws Exception {
                              throwable.printStackTrace();
                              ......
                     }
            });

Get the mobile phone authentication code. jpeg

Maybe

Maybe is a new type of RxJava 2. x, which can be seen as a combination of Single and Comppletable.

After Maybe was created, Maybe Emitter, like Single Emitter, did not have an onNext() method, but also needed to send data through onSuccess().

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });

The printed result is

s=testA

Maybe can only transmit 0 or 1 data, and even if it transmits multiple data, the subsequent data will not be processed.

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onSuccess("testA");
                e.onSuccess("testB");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });

The printed results are still

s=testA

It is consistent with the results of the first execution.

If MaybeEmitter calls onComplete() first, no data will be emitted even if onSuccess() is called later.

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onComplete();
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s="+s);
            }
        });

This time, no data will be printed.

Let's make a change to the above code and add onComplete() to subscribe() to see if the printed result looks like this. Because there is no onComplete() method in SingleObserver.

Maybe.create(new MaybeOnSubscribe<String>() {

            @Override
            public void subscribe(@NonNull MaybeEmitter<String> e) throws Exception {
                e.onComplete();
                e.onSuccess("testA");
            }
        }).subscribe(new Consumer<String>() {

            @Override
            public void accept(@NonNull String s) throws Exception {

                System.out.println("s=" + s);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {

            }
        }, new Action() {
            @Override
            public void run() throws Exception {
                System.out.println("Maybe onComplete");
            }
        });

The result of this printing is

Maybe onComplete

View Maybe-related source code

    @CheckReturnValue
    @SchedulerSupport(SchedulerSupport.NONE)
    public final Disposable subscribe(Consumer<? super T> onSuccess, Consumer<? super Throwable> onError,
            Action onComplete) {
        ObjectHelper.requireNonNull(onSuccess, "onSuccess is null");
        ObjectHelper.requireNonNull(onError, "onError is null");
        ObjectHelper.requireNonNull(onComplete, "onComplete is null");
        return subscribeWith(new MaybeCallbackObserver<T>(onSuccess, onError, onComplete));
    }

As we can see, subscribe calls onComplete() of Maybe Observer when no data is sent. If Maybe sends data or calls onError(), it will not execute onComplete() of Maybe Observer.

We can also convert Maybe to Observable, Flowable, Single, just call toObservable(), toFlowable(), and toSingle() accordingly.

Next, let's look at how Maybe and Retrofit work together.
The following network request initially returns a Flowable type, but this network request is not a continuous flow of events. We only issue a Post request to return data and receive only one event. Therefore, you can consider merging onComplete() with onNext(). Here, try changing Flowable to Maybe.

    @POST("v1/contents")
    Maybe<ContentModel> loadContent(@Body ContentParam param);

In the model class, we will probably write this.

public class ContentModel extends HttpResponse {

    public List<ContentItem> data;

    /**
     * Getting content
     * @param fragment
     * @param param
     * @param cacheKey
     * @return
     */
    public Maybe<ContentModel> getContent(Fragment fragment,ContentParam param,String cacheKey) {

        return apiService.loadContent(param)
                .compose(RxLifecycle.bind(fragment).<ContentModel>toLifecycleTransformer())
                .compose(RxJavaUtils.<ContentModel>maybeToMain())
                .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey,App.getInstance().cache));
    }

    ......
}

Among them, maybeToMain() method is a tool method written by Kotlin, which will be more simple and clear to write by Kotlin, especially lambda expression is more intuitive.

    @JvmStatic
    fun <T> maybeToMain(): MaybeTransformer<T, T> {

        return MaybeTransformer{
            upstream ->
            upstream.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
        }
    }

Finally, the model class is really used. If the network request succeeds, the data is displayed on the recyclerview, and if it fails, it will be processed accordingly.

        model.getContent(this,param,cacheKey)
                .subscribe(new Consumer<ContentModel>() {
                    @Override
                    public void accept(@io.reactivex.annotations.NonNull ContentModel model) throws Exception {

                        adapter = new NewsAdapter(mContext, model);
                        recyclerview.setAdapter(adapter);
                        spinKitView.setVisibility(View.GONE);
                    }
                }, new RxException<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        throwable.printStackTrace();
                        spinKitView.setVisibility(View.GONE);

                        ......
                    }
                });

request.jpeg for getting content

Get response.jpeg for content

summary

RxJava has five different types of observers that can be used to write more concise and elegant code. To some extent, these observers can also make some transformations. It's worth noting that only Flowable supports Backpress, and the other four don't.

Posted by krystof78 on Sat, 08 Jun 2019 12:32:22 -0700