15

I'm using Retrofit to return rxjava Observable's for my async network calls.

I find myself repeating the following invocation:

someApiCall().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

Seems like I'm always subscribing on the IO thread and observing on the Android main thread. This seems to be the best practice that all the resources I found advocate. Perhaps other than long-running computations, I don't quite understand when we would want to deviate from this pattern.

Is there a way to remove this boilerplate by defaulting the subscribeOn and observeOn threads?

Is this a use case for rxjava plugins? (I can't find many examples of their use.)

Can I set the default threads at the network boundary by messing with the retrofit executors?

dsg
  • 12,924
  • 21
  • 67
  • 111

4 Answers4

15

For Observable responses, Retrofit currently sets the subscribeOn as the HTTP executor of the RestAdapter (either provided or the default). This was done to shim RxJava support into the existing behavior.

The plan for 2.0 is to provide the ability to set defaults for both subscribeOn and observeOn explicitly (whether it be both, only one, or neither).

A reason you wouldn't want always want observation on the main thread is if you needed to chain multiple API calls together, for example.

Jake Wharton
  • 75,598
  • 23
  • 223
  • 230
  • in version 1.9, I found that not calling `subscribeOn(something)` (usually `Schedulers.io()`) during instrumentation tests deadlocks. Is that an expected behavior? – njzk2 Apr 16 '15 at 21:01
6

The Change Log of Retrofit Version 2.0.0-beta2 (2015-09-28) shows subscribeOn() is required for running in the background.

Fix: Observable and Single-based execution of requests now behave synchronously (and thus requires subscribeOn() for running in the background).

Xiaozou
  • 1,655
  • 1
  • 15
  • 29
4

Yes, it's possible to remove both calls.

Here is the retrofit adapter class that automatically schedules both subscribeOn and observedOn to remove the need for the boilerplate calls in each invocation:

public class RxThreadingCallAdapterFactory extends CallAdapter.Factory {
    private final RxJava2CallAdapterFactory original;

    private RxThreadingCallAdapterFactory() {
        // Always call on background thread
        original = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io());
    }

    public static CallAdapter.Factory create() {
        return new RxThreadingCallAdapterFactory();
    }

    @Override
    public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        return new RxCallAdapterWrapper(original.get(returnType, annotations, retrofit));
    }

    private static class RxCallAdapterWrapper implements CallAdapter<Observable<?>> {
        private final CallAdapter<?> wrapped;

        public RxCallAdapterWrapper(CallAdapter<?> wrapped) {
            this.wrapped = wrapped;
        }

        @Override
        public Type responseType() {
            return wrapped.responseType();
        }

        @Override
        public <R> Observable<?> adapt(Call<R> call) {
            Observable observable = (Observable) wrapped.adapt(call);

            // Always handle result on main thread
            return observable.observeOn(AndroidSchedulers.mainThread());
        }
    }
}

Then use this adapter when configuring retrofit:

Retrofit.Builder()
    .baseUrl(...)
    .addCallAdapterFactory(RxThreadingCallAdapterFactory.create())

I wrote this blog post that goes into a lot of detail on exactly what's happening here.

This will remove both calls, which I consider boilerplate. I consider Jake's scenario of chaining together background calls to not really apply, because in this case I would do retrofit synchronous calls and not use schedulers at all.

Greg Ennis
  • 14,917
  • 2
  • 69
  • 74
3

it is not the full answer to what you are looking for, but this at least removes the burden of righting subscribeOn(Schedulers.io())

retrofit = new Retrofit
            .Builder()
            .baseUrl(app.getUrlBase())
            .client(httpClient)
            .addCallAdapterFactory(
RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()) // <-- default subscribeOn() 
)
                .addConverterFactory(jsonFactory)
                .build();