javaandroidrx-javareactive-programmingrx-android

Emit single item while update UI about progress in RxJava / RxAndroid


I'm currently trying to learn RxJava in Android. I require some guides. At the moment, I'm trying to rewrite AsyncTask below to RxJava:

public class MyAsyncTask extends AsyncTask<Void, ProgressInfo, Result> {
    @Override
    protected Result doInBackground(Void... void) {
        //Long running task
        publishProgress(progressInfo);
        //Long running task
        return result;
    }
    @Override
    protected void onProgressUpdate(ProgressInfo... progressInfo) {
        //Update the progress to UI using data from ProgressInfo
    }
    @Override
    protected void onPostExecute(Result res) {
        //Task is completed with a Result
    }
}

In AsyncTask approach shown above, I can update the UI about the progress by making use of onProgressUpdate method, I pack every data I needed into ProgressInfo and reflect the UI in onProgressUpdate. After task ends, the Result will be passed from from doInBackground to onPostExecute.

But, when I'm trying to implement this with RxJava, I have a hard time dealing with it. Since I cannot pass any parameter to onComplete in Observer. And thus, I ended up with following implementation. I merged the pass of the ProgressInfo and Result into onNext.

 Observable.create(emitter -> {
                //Long running task
                emitter.onNext(progressInfo);
                //Long running task
                emitter.onNext(result);
            }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(object -> {
                    if(object instanceof ProgressInfo){
                        //Update the progress to UI using data from ProgressInfo
                    }else if(object instanceof Result){
                        //Task is completed with a Result
                    }
                });

QUESTION 1: Is my implementation/concept in RxJava right or wrong?

Although it works, I personally feels the implementation above strange and wrong to me. Since the task ultimately is just trying to do some calculations and come out with a single item - Result. The emission of ProgressInfo is like a "side" thing but not "main" thing. I should implement it with Single.create(). But if I did this, I cannot think of any way to pass any ProgressInfo to my UI.

QUESTION 2: Is there a better idea/way to emit single item while updating the UI during the process?

If yes, how would you implement this logic in RxJava? Can you show me your codes/examples?


Solution

  • QUESTION 1: Is my implementation/concept in RxJava right or wrong?

    Surely it depends on your use-case. If you want to provide feedback on each progress-step, there is no way, which I am aware of, to do it differently. I would recommand to provide progress feedback, when the task takes quite a few time and you are able to provide meaningful progress-information.

    Either use a union of ProgressInfo and Result in one type and test for null or use a marker interface, from which ProgressInfo and Result inherite from.

    interface ResultT { }
    
    final class ProgressInfo implements ResultT { }
    
    final class Result implements ResultT { }
    

    When the result is emitted via onNext, I would recommand to complete the observable, in order to give notice to the subscriber, that the task has been done. The subscriber will receive the result via onNext and a onComplete afterwards.

    Observable.<ResultT>create(emitter -> {
            emitter.onNext(progressInfo);
            emitter.onNext(result);
    
            emitter.onComplete();
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(object -> {
                    if (object instanceof ProgressInfo) {
                        //Update the progress to UI using data from ProgressInfo
                    } else if (object instanceof Result) {
                        //Task is completed with a Result
                    }
                });
    

    If you have no meaningfull progress-information, I would recommend using a Single.

    QUESTION 2: Is there a better idea/way to emit single item while updating the UI during the process?

    The doOn*-Operators could be used, to update the UI on subscription and termination. This way is one of the easiest, but could cause problems, when events from other subscriptions interleave with UI changes^1

    .doOnSubscribe(disposable -> {/* update ui */})
                .subscribe(s -> {
                            // success: update ui
                        },
                        throwable -> {
                            // error happened: update ui
                        },
                        () -> {
                            // complete: update ui
                        });
    

    My recommandation would be modelling all States (e.g. Success/ Error) via a class and switch-case in the the subscribe-method (see ^1). First emit an StartProgress-event, then the ProgressInformation ones and on finish the SucessResult. Catch any errors with onError*-operators and return a FailureResult, which contains a error-message and maybe the throwable.

    Observable.<ResultT>create(emitter -> {
            emitter.onNext(progressInfo);
            emitter.onNext(result);
    
            emitter.onComplete();
        }).startWith(new StartProgress())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .onErrorReturn(throwable -> new FailureResult(throwable))
                .subscribe(object -> {
                    // when StartProgress -> updateUI
                    // when ProgressInformation -> updateUI
                    // ...
                });
    

    ^1 http://hannesdorfmann.com/android/mosby3-mvi-1