1

Here's my code

final ApiInterface apiInterface=restAdapter.create(ApiInterface.class);
apiInterface.submitDataToAnalyze("dataToAnalyze","852741963",1,"123","lalala","2015-11-20")
   .flatMap(new Func1<BasicResponse, Observable<?>>() {
    @Override
    public Observable<?> call(BasicResponse basicResponse) {
        if (basicResponse.getResult() == 1){
           return apiInterface.getSuggestion("dataToAnalyze","852741963",1,"123","lalala","2015-11-20");
         }else{
            return null; //error
         }
  }
}).flatMap(new Func1<Observable<?>, Integer>() {
    @Override
    public Integer call(Observable<?> input) {
        if (input == null){
            //update a table
            return 0;
        }else{
            //update another table using data retrieved from apiInterface.getSuggestion
           return 1;
        }
    }
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());

What should I do, so that when the call completes, either at

return apiInterface.getSuggestion()

or

return Observable.just(null)

Two different function (one for each case) would be called? And of course, the function need to work with the data returned from the call.

Btw the second flatMap returns this error:

'flatMap(rx.functions.Func1<? super java.lang.Object,? extends rx.Observable<?>>)' in 'rx.Observable' cannot be applied to '(anonymous rx.functions.Func1<rx.Observable<?>,java.lang.Integer>)'

If it helps, here's my interface for submitDataToAnalyze and getSuggestion

@POST("/request.php")
Observable<BasicResponse> submitDataToAnalyze(@Query("action") String action, @Query("auth") String auth, @Query("ver") int ver, @Query("uuid") String uuid, @Query("dataToAnalyze") String dataToAnalyze,
                              @Query("requestForDate") String requestForDate);

@POST("/request.php")
Observable<BasicResponse> getSuggestion(@Query("action") String action, @Query("auth") String auth, @Query("ver") int ver, @Query("uuid") String uuid, @Query("dataToAnalyze") String dataToAnalyze,
                             @Query("requestForDate") String requestForDate);
Zulan
  • 21,896
  • 6
  • 49
  • 109
imin
  • 4,504
  • 13
  • 56
  • 103
  • What do you want to happen if `basicResponse.getResult != 1`? Would `return Observable.just(null)` do the trick? Or maybe `return Observable.error(new SomeException())`? Or maybe `return Observable.empty()`? – david.mihola Nov 20 '15 at 15:30

3 Answers3

4

Regarding the difference between map and flatMap - there's really no easy answer that applies to all situations. In general I approach it the like this:

I use map for all easy one-to-one transformations: Every X that comes down the chain should be transformed to one Y.

Some examples:

  • transform a String to another String by upper casing it
  • transform a Pair<Integer, Integer> to an Integer by calculating the sum
  • transform a List<Double> to a Double by finding the maximum As you can see X and Y can be of any types (and indeed one or both could be a complex data type like a Collection) - but still, for each input entity you get an output entity.

I use flatMap for situations where at least one of the following applies:

  • the transformation is by its nature asynchronous, for example a network request
  • the transformation does not necessarily yield one output for each input, for example it could produce one or more outputs for each input
  • the transformation has some obvious error or error-like conditions that should be addressed right there and not be propagated down the chain - I think your example falls under that case (as well as being an async network call).

If these three conditions sound familiar, that's because Erik Meijer listed them in his talk here: https://www.youtube.com/watch?v=sTSQlYX5DU0

Regarding the compile error in your updated code - the correct type declarations should look like this:

apiInterface.submitDataToAnalyze("dataToAnalyze","852741963",1,"123","lalala","2015-11-20")
.flatMap(new Func1<BasicResponse, Observable<BasicResponse>>() {
    @Override
    public Observable<BasicResponse> call(BasicResponse basicResponse) {
        if (basicResponse.getResult() == 1){
            return apiInterface.getSuggestion("dataToAnalyze","852741963",1,"123","lalala","2015-11-20");
        } else {
            return Observable.just(null);
        }
    }
})
.flatMap(new Func1<BasicResponse, Observable<Integer>>() {
    @Override
    public Observable<Integer> call(BasicResponse input) {
        if (input == null){
            //update a table
            return Observable.just(0);
        } else {
            //update another table using data retrieved from apiInterface.getSuggestion
           return Observable.just(1);
        }
    }

Since getSuggestion returns an Observable<BasicResponse> that must be the return type of the first Func1 - even if the actual value returned must be null.

I think your misunderstanding lies at what follows, so let's have a look. RxJava does then not pass the Observable<BasicResponse> down the chain - since you are using flatMap it will then internally subscribe to the Observable<BasicResponse> that you've created and will then take each item emitted by that Observable and pass that down. That is also why the next Func1 must be a Func1<BasicResponse, Observable<Integer>> - and not a Func1<Observable<BasicResponse>, Observable<Integer>>. The input type of any function waiting down the chain must be BasicReponse because that is the type of the "unpacked" items from your Observable<BasicResponse>. I hope this was clear, if a bit verbose.

To come back to map vs. flatMap for the final part - you can use either one. flatMap always works, if you are using it correctly. But map can often be shorter (and probably a bit more efficient, since it does not create any more internal Observables). In general, if you have a flatMap and in each branch you just return an Observable.just() you could just as well use a simple map. So you could probably rewrite your code as:

.map(new Func1<BasicResponse, Integer>() {
    @Override
    public Integer call(BasicResponse input) {
        if (input == null){
            //update a table
            return 0;
        } else {
            //update another table using data retrieved from apiInterface.getSuggestion
           return 1;
        }
    }
david.mihola
  • 12,062
  • 8
  • 49
  • 73
  • Thank you very much. Your explanation clears many thing to me in trying to understand RxJava! – imin Nov 23 '15 at 16:09
1

The flatMap should return an observable, so instead of return null it should be return Observable.just(null). Returning null directly will result in a NPE when you chain other operators to the flatmap.

After that you can chain the next operation. What operator to use really depends on what you want that function to do.

memoizr
  • 2,081
  • 2
  • 18
  • 24
  • I'm still trying to get the hang of chaining flatmap. I tried to chain another operation as you sugguested, using flatmap but I got the error flatMap could not be applied to... I have updated my question above with the error. – imin Nov 22 '15 at 17:27
  • I just re-read tutorials and documentation regarding flatmap, and it seems it should return observables too. Should I do that, or should I use map instead? From what I understand from this answer http://stackoverflow.com/a/27372466, map can return other data type right? – imin Nov 22 '15 at 18:09
1

Two suggestions:

Use filter:

apiInterface.submitDataToAnalyze(...)
  .filter(br -> br.getResult() == 1)
  .flatMap(new Func1<BasicResponse, Observable<?>>() {
    @Override
    public Observable<?> call(BasicResponse basicResponse) {
       return apiInterface.getSuggestion(...);
    }
  })
...

or return Observable.empty() in the flatMap:

if (basicResponse.getResult() == 1){
    return apiInterface.getSuggestion(...);
}else{
    return Observable.empty();
}
Dave Moten
  • 11,957
  • 2
  • 40
  • 47
  • What if for my case, I don't want to use filter, instead I want to chain another flatmap.. can you please see my updated question? – imin Nov 22 '15 at 17:28
  • I just re-read tutorials and documentation regarding flatmap, and it seems it should return observables too. Should I do that, or should I use map instead? From what I understand from this answer http://stackoverflow.com/a/27372466, map can return other data type right? – imin Nov 22 '15 at 18:09