3

I have the following RxJava Observable:

final class MapBitmapObservable {

    static Observable<Bitmap> create(@NonNull final MapView mapView) {
        return Observable.create(new Observable.OnSubscribe<Bitmap>() {
            @Override
            public void call(final Subscriber<? super Bitmap> subscriber) {
                mapView.getMapAsync(new OnMapReadyCallback() {
                    @Override
                    public void onMapReady(@NonNull final GoogleMap googleMap) {
                        googleMap.snapshot(new GoogleMap.SnapshotReadyCallback() {
                            @Override
                            public void onSnapshotReady(@Nullable final Bitmap bitmap) {
                                if (bitmap != null) {
                                    subscriber.onNext(bitmap);
                                    subscriber.onCompleted();
                                } else {
                                    subscriber.onError(new MapSnapshotFailedException());
                                }
                            }
                        });
                    }
                });
            }
        });
    }

    private MapBitmapObservable() {

    }

}

The MapView method getMapAsync must be called on the main thread to avoid this exception:

java.lang.IllegalStateException: getMapAsync() must be called on the main thread
    at com.google.android.gms.common.internal.zzx.zzcD(Unknown Source)
    at com.google.android.gms.maps.MapView.getMapAsync(Unknown Source)
    at com.github.stkent.bugshaker.email.screenshot.maps.MapBitmapObservable$1.call(MapBitmapObservable.java:42)
    at com.github.stkent.bugshaker.email.screenshot.maps.MapBitmapObservable$1.call(MapBitmapObservable.java:37)
    at rx.Observable.unsafeSubscribe(Observable.java:8098)
    ...

Assume the MapBitmapObservable is used as part of an Observable chain in which previous and subsequent operations are potentially long-running and should be executed off the main thread. A simplified example could look like this:

Observable.just(activity)
        .flatmap(new Func1<Activity, Observable<MapView>>() {
            @Override
            public Observable<Bitmap> call(@NonNull final Activity activity) {
                return ExpensiveToCreateObservable.create(activity);
            }
        })
        .flatmap(new Func1<MapView, Observable<Bitmap>>() {
            @Override
            public Observable<Bitmap> call(@NonNull final MapView mapView) {
                return MapBitmapObservable.create(mapView);
            }
        })
        .flatmap(new Func1<Bitmap, Observable<Uri>>() {
            @Override
            public Observable<Uri> call(@NonNull final Bitmap bitmap) {
                return SomeOtherExpensiveToCreateObservable.create(bitmap);
            }
        })
        .subscribeOn(Schedulers.io())
        .subscribe();

(although it should be noted that in my actual application, the chaining is spread across several different methods). I would like to:

  1. make sure that MapView.getMapAsync is called on the main thread;
  2. allow the second long-running operation to execute on the original Scheduler, whatever that may have been (Schedulers.io(), Schedulers.computation(), etc.)

In my mind, pseudocode to achieve this would look something like:

Observable.just(activity)
        .flatmap(new Func1<Activity, Observable<MapView>>() {
            @Override
            public Observable<Bitmap> call(@NonNull final Activity activity) {
                return ExpensiveToCreateObservable.create(activity);
            }
        })
        .observeOn(AndroidSchedulers.mainThread()) // This is real, and resolves bullet 1.
        .flatmap(new Func1<MapView, Observable<Bitmap>>() {
            @Override
            public Observable<Bitmap> call(@NonNull final MapView mapView) {
                return MapBitmapObservable.create(mapView);
            }
        })
        .observeOn(/* Some way of referencing the thread on which I originally subscribed, to resolve bullet 2. */)
        .flatmap(new Func1<Bitmap, Observable<Uri>>() {
            @Override
            public Observable<Uri> call(@NonNull final Bitmap bitmap) {
                return SomeOtherExpensiveToCreateObservable.create(bitmap);
            }
        })
        .subscribeOn(Schedulers.io()) // I do not want to rely on knowledge of the Scheduler type used at this call-site. 
        .subscribe();

Is this possible?

stkent
  • 19,772
  • 14
  • 85
  • 111
  • Keep a reference of the original `Scheduler` and use it later? – Aaron He Jan 28 '16 at 06:50
  • Thanks for the input! This would be feasible if the chain was all constructed in-line, as in the simplified example above; however, the following caveat from the original question: "(although it should be noted that in my actual application, the chaining is spread across several different methods)" means that `subscribeOn` is called far from the `flatmap` that uses the `MapBitmapObservable`. This means I would ideally like a more 'localized' solution, where the two `observeOn` calls that wrap said `flatmap` do not need to know the original `Scheduler` type. – stkent Jan 28 '16 at 16:15
  • (as defined at the call site). – stkent Jan 28 '16 at 16:21

1 Answers1

1

From the observeOn() documentation:

ObserveOn, on the other hand, affects the thread that the Observable will use below where that operator appears. For this reason, you may call ObserveOn multiple times at various points during the chain of Observable operators in order to change on which threads certain of those operators operate.

So as mentioned by Aaron He, you could keep some reference to the Scheduler you are using use it on the latter "observeOn".


Another approach to do this that I sometimes use, is to remove both "observeOn" function, and make sure View items are being handled on UI thread by Activity.runOnUiThread. Something like -

static Observable<Bitmap> create(@NonNull final Activity activity,@NonNull final SomeObject someObject) {
        return Observable.create(new Observable.OnSubscribe<Pair<Activity,SomeObject>>() {
            @Override
            public void call(final Subscriber<? super Pair<Activity,SomeObject>> subscriber) {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        someObject.doStuff();
                    }
                });  
            }
        });
    }
Yoshkebab
  • 770
  • 1
  • 7
  • 14
  • Good ideas! I responded to the former as a comment on the original question. Regarding the latter: I'm curious, when you use this pattern, do you typically call the subscriber methods (e.g. `subscriber.onNext`) inside the `Runnable`, or directly inside the `call` method? I imagine calling inside the `Runnable` is what I'd want to try here. – stkent Jan 28 '16 at 16:19
  • 1
    It depends on the action I need to do. I only use the `Runnable` when I need to update the UI. So if, for example, after I'm done updating I want to call `onNext` it's will go in the `Runnable`. However usually this is not the case, and I'm calling it directly from the `call` method. – Yoshkebab Feb 09 '16 at 14:39