Adi
Adi

Reputation: 4230

How to pass output of a single to a completable in RxJava?

I want to call an API api1 and pass the output returned by the API to a second API api2. The first API is a get request and second API is a POST request. Therefore, the api1 returns a Single<String> and api2 returns a Completable.

The functions look something like this:

// api1
public Single<String> getToken() {
  ...
}
// api2
public Completable saveTokenToBackend(String token, String userId) {
 ...
}

I want to chain both of these operations together. The subscriber is only interested in knowing if the process of getting token and saving it was successful or not. Therefore, the return type of the final chain of operations should be a Completable. However, when i do this the API calls for api2 stop happening. Only api1 works successfully as per logs.

Single<Completable> r1 = getToken().map(t -> saveTokenToBackend(t, userId));
Completable r2 = Completable.fromSingle(r1);

My broader question here is that how do i chain a response from a Single to a Completable?

Second question is why the above code is not working?

::EDIT::

Based on suggestions in comments, I tried:

public Completable getAndSaveToken() {
    getToken().flatMapCompletable(t -> saveTokenToBackend(t, "dummyuser");
}

In my application code i am doing:

getAndSaveToken()
   .subscribeOn(Schedulers.io())
   .observeOn(AndroidSchedulers.mainThread())
   .subscribe(() -> {
                Log.v(TAG, "call success");
    }, error -> {
                Log.e(TAG, "Error", error);
    });

Results in: java.io.IOException: Must not be called on the main application thread

Upvotes: 3

Views: 5337

Answers (1)

Ignacio Baca
Ignacio Baca

Reputation: 1578

You have 2 different problems, composition and scheduling. Context: getToken return a single and saveToken(token) returns a completable.

composition: to compose a single and a completable, as you have already noticed, you can use the flatMap operator, this returns a new completable that first gets the token, and then save it (probably, you'll want to modify the token before saving it ;)

getToken().flatMapCompletable(n -> saveToken(n)) // returns a completable

If you want to keep it as a single, so you can map it back to the first instance:

getToken().flatMap(n -> saveToken(n).toSingleDefault(n)) // returns a single

scheduling: in android, you must not start a request in the main thread. To avoid it you can use the subscribeOn operator, also looks like you have already noticed that

getToken().subscribeOn(io()).doOnNext(n -> {/*this should be evaluated in io thread*/})
getToken().subscribeOn(io()).observeOn(mainThread()).doOnNext(n -> {/*this on mainThread*/})

If you still get the error, then the observeOn or subscribeOn has been re-configured somewhere. Need more code to be sure. But anyways, you can assert that both requests are executed in the io thread applying the operator twice:

getToken().subscribeOn(io).flatMapCompletable(token -> saveToken(token).subscribeOn(io()))

Another alternative, if you are using retrofit, is to apply the RxJava2CallAdapterFactory using the createWithScheduler(io()) factory, instead of the default one. This will assert that all requests are created in the io(), so you can combine and prepare the data, and finally apply the observeOn(mainThread()) to update the UI.

Upvotes: 7

Related Questions