When should one use RxJava Observable and when simple Callback on Android?

94,445

Solution 1

For simple networking stuff, the advantages of RxJava over Callback is very limited. The simple getUserPhoto example:

RxJava:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
               // do some stuff with your photo 
            }
     });

Callback:

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess(Photo photo, Response response) {
    }
});

The RxJava variant is not much better than the Callback variant. For now, let's ignore the error handling. Let's take a list of photos:

RxJava:

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            list.add(photo)
        }
    });

Callback:

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        List<Photo> filteredPhotos = new ArrayList<Photo>();
        for(Photo photo: photos) {
            if(photo.isPNG()) {
                filteredList.add(photo);
            }
        }
    }
});

Now, the RxJava variant still isn't smaller, although with Lambdas it would be getter closer to the Callback variant. Furthermore, if you have access to the JSON feed, it would be kind of weird to retrieve all photos when you're only displaying the PNGs. Just adjust the feed to it only displays PNGs.

First conclusion

It doesn't make your codebase smaller when you're loading a simple JSON that you prepared to be in the right format.

Now, let's make things a bit more interesting. Let's say you not only want to retrieve the userPhoto, but you have an Instagram-clone, and you want to retrieve 2 JSONs: 1. getUserDetails() 2. getUserPhotos()

You want to load these two JSONs in parallel, and when both are loaded, the page should be displayed. The callback variant will become a bit more difficult: you have to create 2 callbacks, store the data in the activity, and if all the data is loaded, display the page:

Callback:

api.getUserDetails(userId, new Callback<UserDetails>() {
    @Override
    public void onSuccess(UserDetails details, Response response) {
        this.details = details;
        if(this.photos != null) {
            displayPage();
        }
    }
});

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        this.photos = photos;
        if(this.details != null) {
            displayPage();
        }
    }
});

RxJava:

private class Combined {
    UserDetails details;
    List<Photo> photos;
}


Observable.zip(api.getUserDetails(userId), api.getUserPhotos(userId), new Func2<UserDetails, List<Photo>, Combined>() {
            @Override
            public Combined call(UserDetails details, List<Photo> photos) {
                Combined r = new Combined();
                r.details = details;
                r.photos = photos;
                return r;
            }
        }).subscribe(new Action1<Combined>() {
            @Override
            public void call(Combined combined) {
            }
        });

We are getting somewhere! The code of RxJava is now as big as the callback option. The RxJava code is more robust; Think of what would happen if we needed a third JSON to be loaded (like the latest Videos)? The RxJava would only need a tiny adjustment, while the Callback variant needs to be adjusted in multiple places (on each callback we need to check if all data is retrieved).

Another example; we want to create an autocomplete field, which loads data using Retrofit. We don't want to do a webcall every time an EditText has a TextChangedEvent. When typing fast, only the last element should trigger the call. On RxJava we can use the debounce operator:

inputObservable.debounce(1, TimeUnit.SECONDS).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                // use Retrofit to create autocompletedata
            }
        });

I won't create the Callback variant but you will understand this is much more work.

Conclusion: RxJava is exceptionally good when data is sent as a stream. The Retrofit Observable pushes all elements on the stream at the same time. This isn't particularly useful in itself compared to Callback. But when there are multiple elements pushed on the stream and different times, and you need to do timing-related stuff, RxJava makes the code a lot more maintainable.

Solution 2

The Observable stuff is already done in Retrofit, so the code could be this:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
         @Override
            public void call(Photo photo) {
                //save photo?
            }
     });

Solution 3

In the case of getUserPhoto() the advantages for RxJava aren't great. But let's take another example when you'll get all the photos for a user, but only when the image is PNG, and you don't have access to the JSON to do the filtering on the serverside.

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            // on main thread; callback for each photo, add them to a list or something.
            list.add(photo)
        }
    }, 
    new Action1<Throwable>() {
    @Override
        public void call(Throwable throwable) {
            // on main thread; something went wrong
            System.out.println("Error! " + throwable);
        }
    }, 
    new Action0() {
        @Override
        public void call() {
            // on main thread; all photo's loaded, time to show the list or something.
        }
    });

Now the JSON returns a list of Photo's. We'll flatMap them to individual items. By doing so, we'll be able to use the filter method to ignore photos which are not PNG. After that, we'll subscribe, and get a callback for each individual photo, an errorHandler, and a callback when all rows have been completed.

TLDR Point here being; the callback only returns you a callback for succes and failure; the RxJava Observable allows you to do map, reduce, filter and many more stuff.

Solution 4

With rxjava you can do more things with less code.

Let´s assume that you want to implement instant search in your app. With callbacks you have worried about unsubscribing the previous request and subscribe to the new one, handle orientation change yourself... I think it´s a lot of code and too verbose.

With rxjava is very simple.

public class PhotoModel{
  BehaviorSubject<Observable<Photo>> subject = BehaviorSubject.create(...);

  public void setUserId(String id){
   subject.onNext(Api.getUserPhoto(photoId));
  }

  public Observable<Photo> subscribeToPhoto(){
    return Observable.switchOnNext(subject);
  }
}

if you want to implement instant search you only have to listen for TextChangeListener and call to photoModel.setUserId(EditText.getText());

In onCreate method of Fragment or activity you subscribe to the Observable that returns photoModel.subscribeToPhoto(), it returns an Observable that always emit the items emited by the latest Observable(request).

AndroidObservable.bindFragment(this, photoModel.subscribeToPhoto())
                 .subscribe(new Action1<Photo>(Photo photo){
      //Here you always receive the response of the latest query to the server.
                  });

Also, if PhotoModel is a Singleton, for instance, you don't need to worry about orientation changes, because BehaviorSubject emits the last server response, regardless of when you subscribe.

With this lines of code we have implemented an instant search and handle orientation changes. Do you think that you can implement this with callbacks with less code? I doubt it.

Solution 5

We usually go with the following logic:

  1. If it's a simple one-response call, then Callback or Future is better.
  2. If it's a call with multiple responses (stream), or when there are complex interaction between different calls (see @Niels' answer), then Observables are better.
Share:
94,445

Related videos on Youtube

Martynas Jurkus
Author by

Martynas Jurkus

Updated on June 09, 2020

Comments

  • Martynas Jurkus
    Martynas Jurkus almost 4 years

    I'm working on networking for my app. So I decided to try out Square's Retrofit. I see that they support simple Callback

    @GET("/user/{id}/photo")
    void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
    

    and RxJava's Observable

    @GET("/user/{id}/photo")
    Observable<Photo> getUserPhoto(@Path("id") int id);
    

    Both look pretty similar at first glance, but when it gets to implementation it gets interesting...

    While with simple callback implementation would look similar to this:

    api.getUserPhoto(photoId, new Callback<Photo>() {
        @Override
        public void onSuccess() {
        }
    });
    

    which is quite simple and straightforward. And with Observable it quickly gets verbose and quite complicated.

    public Observable<Photo> getUserPhoto(final int photoId) {
        return Observable.create(new Observable.OnSubscribeFunc<Photo>() {
            @Override
            public Subscription onSubscribe(Observer<? super Photo> observer) {
                try {
                    observer.onNext(api.getUserPhoto(photoId));
                    observer.onCompleted();
                } catch (Exception e) {
                    observer.onError(e);
                }
    
                return Subscriptions.empty();
            }
        }).subscribeOn(Schedulers.threadPoolForIO());
    }
    

    And that is not it. You still have to do something like this:

    Observable.from(photoIdArray)
            .mapMany(new Func1<String, Observable<Photo>>() {
                @Override
                public Observable<Photo> call(Integer s) {
                    return getUserPhoto(s);
                }
            })
            .subscribeOn(Schedulers.threadPoolForIO())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<Photo>() {
                @Override
                public void call(Photo photo) {
                    //save photo?
                }
            });
    

    Am I missing something here? Or is this a wrong case to use Observables? When would/should one prefer Observable over simple Callback?

    Update

    Using retrofit is much simpler than example above as @Niels showed in his answer or in Jake Wharton's example project U2020. But essentially the question stays the same - when should one use one way or the other?

    • letroll
      letroll almost 10 years
      can you update your link to the file you're talking about in U2020
    • Martynas Jurkus
      Martynas Jurkus almost 10 years
      It is still working...
    • Lo-Tan
      Lo-Tan about 9 years
      Man I had the same thoughts exactly when I was reading RxJava was the new thing. I read a retrofit example (because I'm extremely familiar with it) of a simple request and it was ten or fifteen lines of code and my first reaction was you gotta be kidding me =/ . I also can't figure out how this replaces an event bus, as event bus decouples you from the observable and rxjava reintroduces the coupling, unless I'm mistaken.
  • Yehosef
    Yehosef about 10 years
    Thanks for the answer. I see what your are saying but I'm not sure yet how I would implement this. Could you provide a simple example using the existing retrofit implementation so I can award you the bounty?
  • Christopher Perry
    Christopher Perry about 10 years
    Yes, but the question is when to prefer one to the other?
  • Martynas Jurkus
    Martynas Jurkus about 10 years
    So how is this better than simple callback?
  • ataulm
    ataulm about 10 years
    @MartynasJurkus observables can be useful if you want to chain several functions. For example, the caller wants to get a photo that is cropped to dimensions 100x100. The api may return any size photo, so you can map the getUserPhoto observable to another ResizedPhotoObservable - the caller only gets notified when the resizing is done. If you don't need to use it, don't force it.
  • hiBrianLee
    hiBrianLee over 9 years
    One minor feedback. There's no need to call .subscribeOn(Schedulers.io()) as RetroFit already takes care of this - github.com/square/retrofit/issues/430 (see Jake's reply)
  • Dmitry Zaytsev
    Dmitry Zaytsev over 9 years
    @MartynasJurkus additionally to stuff said by ataulm, unlike Callback you can unsubscribe from Observeable, therefore avoiding common lifecycle problems.
  • Aravind Yarram
    Aravind Yarram over 9 years
    @DmitryZaitsev can you explain with some example on what you mean by life-cycle problems
  • Dmitry Zaytsev
    Dmitry Zaytsev over 9 years
    @Pangea for instance, having Callback called after Fragment was destroyed. Just as a reminder - this is a question with android tag :)
  • Admin
    Admin about 9 years
    First off subscribeOn and observeOn are not necessary with retrofit. It will do the network call asynchronously and notify on the same thread as the caller. When you add RetroLambda as well to get lambda expressions it becomes much nicer.
  • SERG
    SERG about 9 years
    but i can do it (filter image is PNG) in the .subscribe() Why should i use filter? and there is no need in flatmap
  • Migore
    Migore almost 9 years
    What class did you use for the inputObservable? I have a lot of problems with debouncing and would like to learn a little more about this solution.
  • blizzard
    blizzard over 8 years
    @Migore RxBinding project has the "Platform binding" module that provides the classes like RxView, RxTextView etc that can be used for the inputObservable.
  • Nicolas Jafelle
    Nicolas Jafelle over 8 years
    @Niels can you explain how to add error handling? can you create a subscriber with onCompleted, onError and onNext if you flatMap, Filter and then subscribe? Thank you so much for your big explanation.
  • Ye Lin Aung
    Ye Lin Aung about 8 years
    This is a terrific example!
  • Denis Nek
    Denis Nek about 8 years
    Best explanation ever!
  • Pavitra Kansara
    Pavitra Kansara about 8 years
    @Niels Great explanation! How would you make calls to upload data ( 1-Upload Details first -> 2- get Id as response -> 3- Upload Photo using the id retrieved from response)? How effectively I can use RxJava for that sort of asyn calls?
  • Raymond Chenon
    Raymond Chenon almost 8 years
    3 answers from @Niels. The first one answers the question. No benefit for the 2nd
  • sha
    sha almost 8 years
    Couldn't ask for a better explanation. Convinced . thumbs up
  • Jenya Kirmiza
    Jenya Kirmiza over 7 years
    I see here some troubles. Im not sure but the things after observeOn will be done in the mainThread. How to do it in the background?
  • Greg Ennis
    Greg Ennis over 7 years
    Not really convincing to me. I find that Kotlin has all these operators and much more, using Kotlin is a better experience and makes RxJava's still too verbose and confusing.
  • Christian García
    Christian García over 7 years
    @Niels, this is, by far, the best argument I've seen in favor of RxJava vs Callbacks. I strongly suggest this should be a blog post on its own!
  • forresthopkinsa
    forresthopkinsa almost 7 years
    OP wasn't asking for suggestions for a new library
  • Philipus Silaen
    Philipus Silaen over 6 years
    GGWP!! Great Explanation!!
  • Santanu Sur
    Santanu Sur over 6 years
    what is new Func2 can you please elaborate ? for the combined example of rxjava observable.zip
  • Niels
    Niels over 6 years
    It is a method in RxJava2, which is replaced in RxJava 2 by BiFunction.
  • Amine Harbaoui
    Amine Harbaoui about 6 years
    Nice Explanation Good Job
  • Mayank Sharma
    Mayank Sharma over 5 years
    same answer as first
  • Farid
    Farid almost 5 years
    Don't you think making PhotoModel class a Singleton is itself a restriction? Imagine it's not a user profile but set of photos for multiple users, won't we get only the last requested user photo? Plus requesting a data on every orientation change sounds a bit off to me, what do you think?
  • Farid
    Farid almost 5 years
    @Yehosef did someone delete their comment or you were pretending to be OP? ))
  • Yehosef
    Yehosef almost 5 years
    You can award a bounty on someone else's question. When I asked this, I really needed the answer to the question - so I offered a bounty. If you hover on the bounty award, you'll see that it was awarded by me.
  • Reza
    Reza over 3 years
    It was Practically an awesome example. cause I remember when I had to call two different network request of weather info, one for the current time and one for three days and after both have meet successful the UI had to update it self with it. It was a pain in the neck achieving that without RxJava.