Should I use Observable(RxJava2) or Call (retrofit2)?
Asked Answered
U

1

6

TL;DR: I want to execute multiple Calls (Retrofit) like you can .zip() multiple Observables (RxJava2).


I have a retrofit2 function:

@GET("/data/price")
Call<JsonObject> getBookTitle(@Query("id") String id, @Query("lang") String lang);

I can execute it (async) in code with enquene():

ApiProvider.getBooksAPI().getBookTitle(bookId, "en").enqueue(new Callback<JsonObject>() {
    @Override
    public void onResponse(Call<JsonObject> call, Response<JsonObject> response) { }

    @Override
    public void onFailure(Call<JsonObject> call, Throwable t) { }
});

Now I want to execute multiple Calls at once (get multiple book titles) and be notified when all requests are done. Here is when I am missing knowledge.

I know I could start using Observable (RXJava2) instead of Call (Retrofit2):

@GET("/data/price")
Observable<JsonObject> getBookTitle(@Query("id") String id, @Query("lang") String lang);

and then merge calls like in below example. But this code seems much more complex and long (especially if I only need 1 book title). Isn't there any way I could merge Calls without using Observable?

List<Observable<JsonObject>> mergedCalls = new ArrayList<>();
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId1, "en"));
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId2, "en"));
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId3, "en"));

Observable<List<JsonObject>> observable = Observable.zip(calls, responses -> { 
        // merge responses, return List
        ...
    })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io());

observer = new DisposableObserver<List<JsonObject>> () {
    @Override
    public void onNext(List<JsonObject> result) { // got all API results }

    @Override
    public void onError(Throwable e) { }

    @Override
    public void onComplete() { }
};

observable.subscribe(observer);
Ultimogeniture answered 29/8, 2019 at 9:46 Comment(0)
F
2

Using RxJava is the easy way of merging Retrofit Calls. Merging Calls manually by enqueuing all Calls and doing something when all of them invoke onResponse, will probably be more complex than simply using Observable.zip(...).

The other choice that you have is using Kotlin coroutines (now Retrofit has out of the box support for them). But that depends on the Kotlin presence in your code and your willingness of using coroutines.


EDIT: (Answering your question from the comment)

If you really think about Calls and RxJava Observables you don't really have to do anything more when using RxJava. When using raw Calls you still have to:

  1. Make sure you're on the right thread if you want to touch Views (observeOn(AndroidSchedulers.mainThread()))
  2. Make sure you're touching network on the right thread (subscribeOn(Schedulers.io()))
  3. Make sure you're not using the response when your Activity/Fragment/Something else is no longer present (disposing of the Disposable in RxJava handles that)

You can significantly simplify your example:

  1. Don't create Observable & Observer. Simply use the subscribe method which returns Disposable. And then maintain just this one Disposable.
  2. You probably don't need onComplete so you can use the simpler version of .subscribe(...)
  3. You can remove the need for .subscribeOn(Schedulers.io()) by properly creating your RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()) when building the Retrofit instance.
BooksApi booksApi = ApiProvider.getBooksAPI();
List<Observable<JsonObject>> mergedCalls = new ArrayList<>();
mergedCalls.add(booksApi.getBookTitle(bookId1, "en"));
mergedCalls.add(booksApi.getBookTitle(bookId2, "en"));
mergedCalls.add(booksApi.getBookTitle(bookId3, "en"));

final Disposable disposable = Observable
        .zip(mergedCalls, responses -> {
            // merge responses, return List
        })
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(list -> {
            // got all API results
        }, throwable -> {

        });

Doing that for one call would be as simple as:

final Disposable disposable = booksApi
        .getBookTitle(bookId1, "en")
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(title -> {
            // got the result
        }, throwable -> {

        });
Fantasist answered 29/8, 2019 at 9:59 Comment(7)
Sadly I don't use Kotlin in this project. Let's say I start using Observable. Is there any shorther way (compared to my last code above) to get just 1 book title? With Call there are no extra objects, but with Observable I need to make Observable, Observer and take care to properly dispose everything etc, which makes code very ugly as opposed to Call.Ultimogeniture
Thank you so much. It is much clearer now! Last thing - can I simply dispose disposable in end of .subscribe()? I want this to execute only once and then I'm done.Ultimogeniture
when you dispose() a Disposable it means that you're no longer interested in anything the observable might produce (results or errors). So disposing a Disposable before you receive the result will mean you won't receive it (the answer to your question is "no"). Typically you would dispose it in the onDestroy-kind of a callback of an Activity or a Fragment...Fantasist
You want to execute it only once but you can't be sure how long it will take. It might take a fraction of a second, or a few seconds (depending on the timeout values). So you can only depend on disposing it when it no longer makes sense to display it to the user (e.g. user closes a related screen).Fantasist
I agree with everything, but I still don't understand why I cant dispose it inside (as last line) of .subscribe lambda. The body of lambda will be executed when we get results. Please see pastebin.com/raw/Zr0xD38x.Ultimogeniture
1. I thought you meant .subscribe(...).dispose() 2. The problem with your suggested approach is that you cannot be sure onNext (so the first Consumer from subscribe method) is called while you still have your UI element visible. If the related UI element is closed, and some time after that you receive your onNext, it might be subject to a memory leak. This depends on your whole setup. Hard to judge from those snippets.Fantasist
Agree about memory leak problem. I will inspect the code deeply and with your tips I'm sure I will handle this.Ultimogeniture

© 2022 - 2024 — McMap. All rights reserved.