Search code examples
androidnullpointerexceptionrx-java2mosby

Avoidng NPE using RxJava2 with Mosby


I've created an application using RxJava along with Retrofit and Mosby/Conductor. Now I have this question regarding view disposal and what goes with it.

Consider the following code.

public class IndexPresenter extends MvpBasePresenter<IIndexView> {
    @Inject
    VideoService videoService;

    private CompositeDisposable compositeDisposable = new CompositeDisposable();

    private String lastVideoId;

    public IndexPresenter(Context context) {
        // Dagger Inject
    }

    @Override
    public void detachView(boolean retainInstance) {
        compositeDisposable.clear();
        super.detachView(retainInstance);
    }

    public void getTimeline() {
        Disposable disposable = videoService
                .getTimeline(null)
                .delay(5, TimeUnit.SECONDS)
                .compose(Util.schedulers())
                .doOnSubscribe(__ -> getView().startLoading())
                .doFinally(() -> getView().stopLoading())
                .doOnError(throwable -> Timber.d(throwable, "failed getting timeline data"))
                .subscribe(timelineResponse -> {
                    getView().onIndexDataLoaded(timelineResponse.getMeta().getArtists(), timelineResponse.getVideos());
                    int size = timelineResponse.getVideos().size();
                    lastVideoId = timelineResponse.getVideos().get(size - 1).getId();
                }, throwable -> getView().onIndexDataFailed());
        compositeDisposable.add(disposable);
    }

    public void loadMore() {
        if (lastVideoId != null && lastVideoId.equals("-1")) {
            return;
        }
        Disposable disposable = videoService
                .getTimeline(lastVideoId)
                .compose(Util.schedulers())
                .doOnSubscribe(__ -> getView().startLoading())
                .doFinally(() -> getView().stopLoading())
                .doOnError(throwable -> Timber.d(throwable, "failed loading more items"))
                .subscribe(timelineResponse -> {
                    getView().onNextPageLoaded(timelineResponse.getVideos());
                    int size = timelineResponse.getVideos().size();
                    if (size == 0) {
                        lastVideoId = "-1";
                    } else {
                        lastVideoId = timelineResponse.getVideos().get(size - 1).getId();
                    }
                }, throwable -> getView().onIndexDataFailed());
        compositeDisposable.add(disposable);
    }
}

What I want to know is that, do I still have to perform the nullity check every time I access getView() or not? Doesn't disposing of all the disposable before detaching take care of this?


Solution

  • IF you unsubscribe / dispose your disposable in detachView() as you did

    AND

    IF RxJava subscription callback onNext() run on the android main thread (I assume Util.schedulers() sets .observeOn(AndroidSchedulers.mainThread()))

    THEN (and only then)

    it is guaranteed that you will never run into the scenario that the getView() returns null.

    Because: detachView() is called by Mosby on android's main thread. If the onNext callback also runs on android's main thread then obviously both run on the same thread and they can never run simultaneously at the same time but rather one after the other. Therefore, the order of execution is either:

    1. onNext() callback --> view is still attached, so view != null
    2. detachView() --> cancel subscriptions, so onNext() is never invoked again and you never run in scenario where view == null

    or

    1. detachView() --> cancel subscriptions, so onNext() is never invoked again
    2. Never happens because subscription is disposed, so never run in scenario where view == null

    In your specific case: I guess you observe on android's main thread (Util.schedulers()) and you also dispose the observable properly, so you will never run in getView() == null