62

I am having a lot of trouble understanding the zip operator in RxJava for my android project. Problem I need to be able to send a network request to upload a video Then i need to send a network request to upload a picture to go with it finally i need to add a description and use the responses from the previous two requests to upload the location urls of the video and picture along with the description to my server.

I assumed that the zip operator would be perfect for this task as I understood we could take the response of two observables (video and picture requests) and use them for my final task. But I cant seem to get this to occur how I envision it.

I am looking for someone to answer how this can be done conceptually with a bit of psuedo code. Thank you

feilong
  • 1,564
  • 3
  • 16
  • 22

8 Answers8

75

Zip operator strictly pairs emitted items from observables. It waits for both (or more) items to arrive then merges them. So yes this would be suitable for your needs.

I would use Func2 to chain the result from the first two observables. Notice this approach would be simpler if you use Retrofit since its api interface may return an observable. Otherwise you would need to create your own observable.

// assuming each observable returns response in the form of String
Observable<String> movOb = Observable.create(...);
// if you use Retrofit
Observable<String> picOb = RetrofitApiManager.getService().uploadPic(...),
Observable.zip(movOb, picOb, new Func2<String, String, MyResult>() {
      @Override
      public MyResult call(String movieUploadResponse, String picUploadResponse) {
          // analyze both responses, upload them to another server
          // and return this method with a MyResult type
          return myResult;
      }
   }
)
// continue chaining this observable with subscriber
// or use it for something else
Anggrayudi H
  • 14,977
  • 11
  • 54
  • 87
inmyth
  • 8,880
  • 4
  • 47
  • 52
  • The problem I have been running into is that I have a view that I fire off the tasks of the video observable and another that does the picture observable and yet another still that should take both of those results and use them for the final observable.. Will zip return to me the results of observables that have already been executed? – feilong May 13 '15 at 17:55
  • I don't think you can execute an observable but I think I understand your point. Observable won't run until a subsriber is attached to it. So the whole observable arrangement has to be done before you attach the subscriber. – inmyth May 13 '15 at 20:02
  • 1
    I agree with your point that "zip operator allow you to compose a result from results of two different observable". Now I want the observable 2 is dependent on observable 1, so observable 1 should execute before observable 2 and then I need to combine the result of both observable. Do we have any operator for this. Can zip do this job. I do not want to use flatMap because it converts one stream into another but here I need to set dependency and then zip the result. Please reply. – Sagar Trehan Apr 15 '16 at 05:21
37

A small example:

val observableOne = Observable.just("Hello", "World")
val observableTwo = Observable.just("Bye", "Friends")
val zipper = BiFunction<String, String, String> { first, second -> "$first - $second" }
Observable.zip(observableOne, observableTwo, zipper)
  .subscribe { println(it) }

This will print:

Hello - Bye
World - Friends

In BiFunction<String, String, String> the first String the type of the first observable, the second String is the type of the second observable, the third String represents the type of the return of your zipper function.


I made a small example that calls two real endpoints using zip in this blog post

Evin1_
  • 12,292
  • 9
  • 45
  • 47
  • nice, in the `Func2<...>`, what are the three strings? is it for the `s`, `-`, and `s2`? – chornge Oct 17 '17 at 21:05
  • 1
    I just updated the answer to handle RxJava2 methods, the first String in the BiFunction is the type of the first observable, the second String is type of the second observable and the third String is the type of the observable that it will generate! – Evin1_ Oct 17 '17 at 22:07
4

Here I have an example that I did using Zip in asynchronous way, just in case you´re curious

      /**
 * Since every observable into the zip is created to subscribeOn a diferent thread, it´s means all of them will run in parallel.
 * By default Rx is not async, only if you explicitly use subscribeOn.
  */
@Test
public void testAsyncZip() {
    scheduler = Schedulers.newThread();
    scheduler1 = Schedulers.newThread();
    scheduler2 = Schedulers.newThread();
    long start = System.currentTimeMillis();
    Observable.zip(obAsyncString(), obAsyncString1(), obAsyncString2(), (s, s2, s3) -> s.concat(s2)
                                                                                        .concat(s3))
              .subscribe(result -> showResult("Async in:", start, result));
}

/**
 * In this example the the three observables will be emitted sequentially and the three items will be passed to the pipeline
 */
@Test
public void testZip() {
    long start = System.currentTimeMillis();
    Observable.zip(obString(), obString1(), obString2(), (s, s2, s3) -> s.concat(s2)
                                                                         .concat(s3))
              .subscribe(result -> showResult("Sync in:", start, result));
}


public void showResult(String transactionType, long start, String result) {
    System.out.println(result + " " +
                               transactionType + String.valueOf(System.currentTimeMillis() - start));
}

public Observable<String> obString() {
    return Observable.just("")
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "Hello");
}

public Observable<String> obString1() {
    return Observable.just("")
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> " World");
}

public Observable<String> obString2() {
    return Observable.just("")
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "!");
}

public Observable<String> obAsyncString() {
    return Observable.just("")
                     .observeOn(scheduler)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "Hello");
}

public Observable<String> obAsyncString1() {
    return Observable.just("")
                     .observeOn(scheduler1)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> " World");
}

public Observable<String> obAsyncString2() {
    return Observable.just("")
                     .observeOn(scheduler2)
                     .doOnNext(val -> {
                         System.out.println("Thread " + Thread.currentThread()
                                                              .getName());
                     })
                     .map(val -> "!");
}

You can see more examples here https://github.com/politrons/reactive

paul
  • 12,873
  • 23
  • 91
  • 153
2

zip operator allow you to compose a result from results of two different observable.

You 'll have to give am lambda that will create a result from datas emitted by each observable.

Observable<MovieResponse> movies = ...
Observable<PictureResponse> picture = ...

Observable<Response> response = movies.zipWith(picture, (movie, pic) -> {
        return new Response("description", movie.getName(), pic.getUrl());

});
dwursteisen
  • 11,435
  • 2
  • 39
  • 36
  • I agree with your point that "zip operator allow you to compose a result from results of two different observable". Now I want the observable 2 is dependent on observable 1, so observable 1 should execute before observable 2 and then I need to combine the result of both observable. Do we have any operator for this. Can zip do this job. I do not want to use flatMap because it converts one stream into another but here I need to set dependency and then zip the result. Please reply. – Sagar Trehan Apr 15 '16 at 05:21
2

i have been searching for a simple answer on how to use the Zip operator, and what to do with the Observables i create to pass them to it, i was wondering if i should call subscribe() for every observable or not, non of these answers were simple to find, i had to figure it out by my self, so here is a simple example for using Zip operator on 2 Observables :

@Test
public void zipOperator() throws Exception {

    List<Integer> indexes = Arrays.asList(0, 1, 2, 3, 4);
    List<String> letters = Arrays.asList("a", "b", "c", "d", "e");

    Observable<Integer> indexesObservable = Observable.fromIterable(indexes);

    Observable<String> lettersObservable = Observable.fromIterable(letters);

    Observable.zip(indexesObservable, lettersObservable, mergeEmittedItems())
            .subscribe(printMergedItems());
}

@NonNull
private BiFunction<Integer, String, String> mergeEmittedItems() {
    return new BiFunction<Integer, String, String>() {
        @Override
        public String apply(Integer index, String letter) throws Exception {
            return "[" + index + "] " + letter;
        }
    };
}

@NonNull
private Consumer<String> printMergedItems() {
    return new Consumer<String>() {
        @Override
        public void accept(String s) throws Exception {
            System.out.println(s);
        }
    };
}

the printed result is :

[0] a
[1] b
[2] c
[3] d
[4] e

the final answers to the questions that where in my head were as follows

the Observables passed to the zip() method just need to be created only, they do not need to have any subscribers to them, only creating them is enough ... if you want any observable to run on a scheduler, you can specify this for that Observable ... i also tried the zip() operator on Observables where they should wait for there result, and the Consumable of the zip() was triggered only when both results where ready (which is the expected behavior)

Ahmed Adel Ismail
  • 2,168
  • 16
  • 23
  • I have two lists of Observables (http requests). The first list contains requests to create items, and the second list contains requests to update items. I want to execute "something" when all of the requests has ended. I was thinking to use zip, but with your example I see it is to group the requests by pairs, and I just want to get notified at the end (the lists have different sizes). Can you give me some idea? Thanks. – JCarlosR May 19 '17 at 13:16
  • 1
    take a look at this : http://reactivex.io/documentation/operators/combinelatest.html – Ahmed Adel Ismail May 22 '17 at 00:16
2

This is my implementation using Single.zip and rxJava2

I tried to make it as easy to understand as possible

//
// API Client Interface
//
@GET(ServicesConstants.API_PREFIX + "questions/{id}/")
Single<Response<ResponseGeneric<List<ResponseQuestion>>>> getBaseQuestions(@Path("id") int personId);

@GET(ServicesConstants.API_PREFIX + "physician/{id}/")
Single<Response<ResponseGeneric<List<ResponsePhysician>>>> getPhysicianInfo(@Path("id") int personId);

//
// API middle layer - NOTE: I had feedback that the Single.create is not needed (but I haven't yet spent the time to improve it)
//
public Single<List<ResponsePhysician>> getPhysicianInfo(int personId) {
    return Single.create(subscriber -> {
        apiClient.getPhysicianInfo(appId)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(response -> {
                    ResponseGeneric<List<ResponsePhysician>> responseBody = response.body();
                    if(responseBody != null && responseBody.statusCode == 1) {
                        if (!subscriber.isDisposed()) subscriber.onSuccess(responseBody.data);
                    } else if(response.body() != null && response.body().status != null ){
                        if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.body().status));
                    } else {
                        if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if(!subscriber.isDisposed()) subscriber.onError(throwable);
                });
    });
}

public Single<List<ResponseQuestion>> getHealthQuestions(int personId){
    return Single.create(subscriber -> {
        apiClient.getBaseQuestions(personId)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(response -> {
                    ResponseGeneric<List<ResponseQuestion>> responseBody = response.body();
                    if(responseBody != null && responseBody.data != null) {
                        if (!subscriber.isDisposed()) subscriber.onSuccess(response.body().data);
                    } else if(response.body() != null && response.body().status != null ){
                        if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.body().status));
                    } else {
                        if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if(!subscriber.isDisposed()) subscriber.onError(throwable);
                });
    });
}

//please note that ResponseGeneric is just an outer wrapper of the returned data - common to all API's in this project

public class ResponseGeneric<T> {

    @SerializedName("Status")
    public String status;

    @SerializedName("StatusCode")
    public float statusCode;

    @SerializedName("Data")
    public T data;
}

//
// API end-use layer - this gets close to the UI so notice the oberver is set for main thread
//
private static class MergedResponse{// this is just a POJO to store all the responses in one object
    public List<ResponseQuestion> listQuestions;
    public List<ResponsePhysician> listPhysicians;
    public MergedResponse(List<ResponseQuestion> listQuestions, List<ResponsePhysician> listPhysicians){
        this.listQuestions = listQuestions;
        this.listPhysicians = listPhysicians;
    }
}

// example of Single.zip() - calls getHealthQuestions() and getPhysicianInfo() from API Middle Layer
private void downloadHealthQuestions(int personId) {
    addRxSubscription(Single
            .zip(getHealthQuestions(personId), getPhysicianInfo(personId), MergedResponse::new)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(response -> {
                if(response != null) {
                    Timber.i(" - total health questions downloaded %d", response.listQuestions.size());
                    Timber.i(" - physicians downloaded %d", response.listPhysicians.size());

                    if (response.listPhysicians != null && response.listPhysicians.size()>0) {
                        // do your stuff to process response data
                    }

                    if (response.listQuestions != null && response.listQuestions.size()>0) {

                        // do your stuff to process response data
                    }


                } else {
                    // process error - show message
                }
            }, error -> {
                // process error - show network error message
            }));
}
Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
1

You use the zip from rxjava with Java 8:

Observable<MovieResponse> movies = ...
Observable<PictureResponse> picture = ...

Observable<ZipResponse> response = Observable.zip(movies, picture, ZipResponse::new);

class ZipResponse {
        private MovieResponse movieResponse;
        private PictureResponse pictureResponse;

        ZipResponse(MovieResponse movieResponse, PictureResponse pictureResponse) {
             this.movieResponse = movieResponse;
             this.pictureResponse = pictureResponse;
        }

        public MovieResponse getMovieResponse() {
             return movieResponse;
        }

        public void setMovieResponse(MovieResponse movieResponse) {
            this.movieResponse= movieResponse;
        }

        public PictureResponse getPictureResponse() {
             return pictureResponse;
        }

        public void setPictureResponse(PictureResponse pictureResponse) {
            this.pictureResponse= pictureResponse;
        }
}
Ângelo Polotto
  • 8,463
  • 2
  • 36
  • 37
0

You can use .zipWith operator for Observable chains.

If uploadMovies() and uploadPictures() return Observable,

uploadMovies()
    .zipWith(uploadPictures()) { m, p ->
        "$m with $p were uploaded"
    }
    .subscribe { print(it) }
antaki93
  • 704
  • 7
  • 10