When do you use map vs flatMap in RxJava?
Say, for example, we want to map Files containing JSON into Strings that contain the JSON--
Using map, we have to deal with the Exception somehow. But how?:
Observable.from(jsonFile).map(new Func1<File, String>() {
#Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// So Exception. What to do ?
}
return null; // Not good :(
}
});
Using flatMap, it's much more verbose, but we can forward the problem down the chain of Observables and handle the error if we choose somewhere else and even retry:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
#Override public Observable<String> call(final File file) {
return Observable.create(new Observable.OnSubscribe<String>() {
#Override public void call(Subscriber<? super String> subscriber) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
subscriber.onNext(json);
subscriber.onCompleted();
} catch (FileNotFoundException e) {
subscriber.onError(e);
}
}
});
}
});
I like the simplicity of the map, but the error handling of flatmap (not the verbosity). I haven't seen any best practices on this floating around and I'm curious how this is being used in practice.
map transform one event to another.
flatMap transform one event to zero or more event. (this is taken from IntroToRx)
As you want to transform your json to an object, using map should be enough.
Dealing with the FileNotFoundException is another problem (using map or flatmap wouldn't solve this issue).
To solve your Exception problem, just throw it with a Non checked exception : RX will call the onError handler for you.
Observable.from(jsonFile).map(new Func1<File, String>() {
#Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// this exception is a part of rx-java
throw OnErrorThrowable.addValueAsLastCause(e, file);
}
}
});
the exact same version with flatmap :
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
#Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
// this static method is a part of rx-java. It will return an exception which is associated to the value.
throw OnErrorThrowable.addValueAsLastCause(e, file);
// alternatively, you can return Obersable.empty(); instead of throwing exception
}
}
});
You can return too, in the flatMap version a new Observable that is just an error.
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
#Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
}
}
});
FlatMap behaves very much like map, the difference is that the function it applies returns an observable itself, so it's perfectly suited to map over asynchronous operations.
In the practical sense, the function Map applies just makes a transformation over the chained response (not returning an Observable); while the function FlatMap applies returns an Observable<T>, that is why FlatMap is recommended if you plan to make an asynchronous call inside the method.
Summary:
Map returns an object of type T
FlatMap returns an Observable.
A clear example can be seen here: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .
Couchbase Java 2.X Client uses Rx to provide asynchronous calls in a convenient way. Since it uses Rx, it has the methods map and FlatMap, the explanation in their documentation might be helpful to understand the general concept.
To handle errors, override onError on your susbcriber.
Subscriber<String> mySubscriber = new Subscriber<String>() {
#Override
public void onNext(String s) { System.out.println(s); }
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) { }
};
It might help to look at this document: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/
A good source about how to manage errors with RX can be found at: https://gist.github.com/daschl/db9fcc9d2b932115b679
In your case you need map, since there is only 1 input and 1 output.
map - supplied function simply accepts an item and returns an item which will be emitted further (only once) down.
flatMap - supplied function accepts an item then returns an "Observable", meaning each item of the new "Observable" will be emitted separately further down.
May be code will clear things up for you:
Observable.just("item1").map( str -> {
System.out.println("inside the map " + str);
return str;
}).subscribe(System.out::println);
Observable.just("item2").flatMap( str -> {
System.out.println("inside the flatMap " + str);
return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);
Output:
inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
The question is When do you use map vs flatMap in RxJava?. And I think a simple demo is more specific.
When you want to convert item emitted to another type , in your case converting file to String, map and flatMap can both work. But I prefer map operator because it's more clearly.
However in some place, flatMap can do magic work but map can't. For example, I want to get a user's info but I have to first get his id when user login in. Obviously I need two requests and they are in order.
Let's begin.
Observable<LoginResponse> login(String email, String password);
Observable<UserInfo> fetchUserInfo(String userId);
Here are two methods, one for login returned Response, and another for fetching user info.
login(email, password)
.flatMap(response ->
fetchUserInfo(response.id))
.subscribe(userInfo -> {
// get user info and you update ui now
});
As you see, in function flatMap applies, at first I get user id from Response then fetch user info. When two requests are finished, we can do our job such as updating UI or save data into database.
However if you use map you can't write such nice code. In a word, flatMap can help us serialize requests.
The way I think about it is that you use flatMap when the function you wanted to put inside of map() returns an Observable. In which case you might still try to use map() but it would be unpractical. Let me try to explain why.
If in such case you decided to stick with map, you would get an Observable<Observable<Something>>. For example in your case, if we used an imaginary RxGson library, that returned an Observable<String> from it's toJson() method (instead of simply returning a String) it would look like this:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
#Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}); // you get Observable<Observable<String>> here
At this point it would be pretty tricky to subscribe() to such an observable. Inside of it you would get an Observable<String> to which you would again need to subscribe() to get the value. Which is not practical or nice to look at.
So to make it useful one idea is to "flatten" this observable of observables (you might start to see where the name _flat_Map comes from). RxJava provides a few ways to flatten observables and for sake of simplicity lets assume merge is what we want. Merge basically takes a bunch of observables and emits whenever any of them emits. (Lots of people would argue switch would be a better default. But if you're emitting just one value, it doesn't matter anyway.)
So amending our previous snippet we would get:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
#Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}).merge(); // you get Observable<String> here
This is a lot more useful, because subscribing to that (or mapping, or filtering, or...) you just get the String value. (Also, mind you, such variant of merge() does not exist in RxJava, but if you understand the idea of merge then I hope you also understand how that would work.)
So basically because such merge() should probably only ever be useful when it succeeds a map() returning an observable and so you don't have to type this over and over again, flatMap() was created as a shorthand. It applies the mapping function just as a normal map() would, but later instead of emitting the returned values it also "flattens" (or merges) them.
That's the general use case. It is most useful in a codebase that uses Rx allover the place and you've got many methods returning observables, which you want to chain with other methods returning observables.
In your use case it happens to be useful as well, because map() can only transform one value emitted in onNext() into another value emitted in onNext(). But it cannot transform it into multiple values, no value at all or an error. And as akarnokd wrote in his answer (and mind you he's much smarter than me, probably in general, but at least when it comes to RxJava) you shouldn't throw exceptions from your map(). So instead you can use flatMap() and
return Observable.just(value);
when all goes well, but
return Observable.error(exception);
when something fails.
See his answer for a complete snippet: https://stackoverflow.com/a/30330772/1402641
Here is a simple thumb-rule that I use help me decide as when to use flatMap() over map() in Rx's Observable.
Once you come to a decision that you're going to employ a map transformation, you'd write your transformation code to return some Object right?
If what you're returning as end result of your transformation is:
a non-observable object then you'd use just map(). And map() wraps that object in an Observable and emits it.
an Observable object, then you'd use flatMap(). And flatMap() unwraps the Observable, picks the returned object, wraps it with its own Observable and emits it.
Say for example we've a method titleCase(String inputParam) that returns Titled Cased String object of the input param. The return type of this method can be String or Observable<String>.
If the return type of titleCase(..) were to be mere String, then you'd use map(s -> titleCase(s))
If the return type of titleCase(..) were to be Observable<String>, then you'd use flatMap(s -> titleCase(s))
Hope that clarifies.
I just wanted to add that with flatMap, you don't really need to use your own custom Observable inside the function and you can rely on standard factory methods/operators:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
#Override public Observable<String> call(final File file) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
return Observable.just(json);
} catch (FileNotFoundException ex) {
return Observable.<String>error(ex);
}
}
});
Generally, you should avoid throwing (Runtime-) exceptions from onXXX methods and callbacks if possible, even though we placed as many safeguards as we could in RxJava.
In that scenario use map, you don't need a new Observable for it.
you should use Exceptions.propagate, which is a wrapper so you can send those checked exceptions to the rx mechanism
Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() {
#Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
throw Exceptions.propagate(t); /will propagate it as error
}
}
});
You then should handle this error in the subscriber
obs.subscribe(new Subscriber<String>() {
#Override
public void onNext(String s) { //valid result }
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};);
There is an excellent post for it: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
RxJava Map vs FlatMap
They both are Transforming operators but map has 1-1 relation and flatMap has 1-0 or many relation.
map and flatmap emits stream with
map- only 1 element
flatmap - 0/many elements
map emits single element and flatmap emits a stream of elements
Map operator
map(new Function<A, B>() {
#Override
public B apply(A a) throws Exception {
B b = new B(a);
return b;
}
})
FlatMap operator
flatMap(new Function<A, ObservableSource<B>>() {
#Override
public ObservableSource<B> apply(A a) throws Exception {
return foo(a);
}
})
[flatMap vs concatMap]
[Swift map vs flatMap]
In some cases you might end up having chain of observables, wherein your observable would return another observable. 'flatmap' kind of unwraps the second observable which is buried in the first one and let you directly access the data second observable is spitting out while subscribing.
Flatmap maps observables to observables.
Map maps items to items.
Flatmap is more flexible but Map is more lightweight and direct, so it kind of depends on your usecase.
If you are doing ANYTHING async (including switching threads), you should be using Flatmap, as Map will not check if the consumer is disposed (part of the lightweight-ness)
Related
I have a function returning an Either<MyError, String> (function2) , which result depends on another function returning another Either<MyError, SomethingElse> (function1)
Both functions rely on a Try block that could fail, and I want to compose those two first function to create a "handle" which will be the main function of my class.
There are basically 3 scenarios possible :
function1 fails : I want handle to return the error given by function1
function1 succeeds and function2 fails : function2 must return its own error then returned by handle
both functions work : handle must return the String
Here is my current code :
private Either<MyError, Path> getPath(Arg arg) { // function 1
return Try.of(() -> //some code that can fail)
.toEither().mapLeft(e -> new MyError("Error message for function1", e));
}
private Either<MyError, String> getContent(Path path) { // function 2
return Try.of(() -> //some code that can fail)
.toEither().mapLeft(e -> new MyError("Error message for function2", e));
}
public Either<MyError, String> handle(Arg arg) {
return Either.right(arg)
.map(this::getPath)
.map(this::getContent);
}
Everything works except the Handle function, I think that my problem might be related to the use of Either::map function, that might not be the thing for my case.
Any thought about this ?
Also, sorry if the answer seems obvious, i am quite new to functionnal programming and vavr.
The method that could help to make this work would be flatMap.
So if you use flatMap instead of map, the handle method will become something like:
public Either<MyError, String> handle(Arg arg) {
return Either.<MyError, Arg>right(arg)
.flatMap(this::getPath)
.flatMap(this::getContent);
}
The scenarios you mentioned are all covered with this flatMap method.
See the Either.flatMap documentation for the official docs about it.
I am looking for what is the recommended practice in rxjava2 to handle a case where one flowable leads to conditional behaviors.
More concretely, I have a Maybe<String> for which I want to Update the String on the database if the String exists or, if it doesn't exists I want to create a new String and save it on the database.
I thought of the below but obviously it is not what I am looking for:
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar"))).subscribe(result ->
System.out.println("save to database "+result));
source.subscribe(result -> System.out.println("update result "+result));
The above obviously produces
save to database foo
update result foo
I tried also the below which gives the expected result but still feel it's... weird.
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar")).doOnSuccess(result ->
System.out.println("save to database "+result))).subscribe();
source.doOnSuccess(result -> System.out.println("update result "+result)).subscribe();
How can I have an action for when the result exists and when it doesn't exists? How is that use case supposed to be handled in rxjava2?
Update 01
I tried the below and it looks cleaner than what I came up with above. Note sure it is recommended rxjava2 practice however...
Maybe.just(new String("foo"))
.map(value -> Optional.of(value))
.defaultIfEmpty(Optional.empty())
.subscribe(result -> {
if(result.isPresent()) {
System.out.println("update result "+result);
}
else {
System.out.println("save to database "+"bar");
}
});
You have the isEmpty() operator that will return you Boolean if the Maybe source is empty or not, and then you can flatMap it and write a if else statement depending on that Boolean
This is a common pattern in our code as well, though in our case the choices are themselves async. You can't get quite the right semantic by simply composing flatMapX and switchIfEmpty (in either order), so I am curious why this isn't part of the API.
Here's what we're doing for now (this example for when the 2 options are both Completables, we have similar things for the other types as well):
public static <T> Completable flatMapCompletable(Maybe<T> target,
#ClosureParams(FirstParam.FirstGenericType.class)
Closure<? extends CompletableSource> completableSupplier,
Supplier<CompletableSource> emptySupplier) {
Maybe<T> result = target.cache();
return result.isEmpty().flatMapCompletable(empty -> {
if (empty) {
return emptySupplier.get();
} else {
return result.flatMapCompletable(completableSupplier::call);
}
});
}
We're using Groovy, so we package these up as extension methods. I'm not thrilled with the need to use cache() so I'm wondering if there is a better alternative. From looking at the code, an operator which basically combines flatMapX and switch looks like it wouldn't be too hard (but I feel like I'm missing something).
Try something like this. checkDB can return a Maybe or Single or whatever which emits either an optional or a wrapper Object.
checkDB(String)
.flatMap(s -> {
if (s.isPresent()) {
return updateDB(s.get());
} else {
return insertDB("new String");
}
})
There is an solution using the flatMap call with 3 params
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
Function {
update(message) // onSuccess update call returns Single
},
Function {
Single.error(it) // onError
},
Callable {
add(message) // onComplete add call returns Single
}
)
}
}
Or shorter version
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
{
update(message) // onSuccess update call returns Single
},
{
Single.error(it) // onError
},
{
add(message) // onComplete add call returns Single
}
)
}
}
I'm trying to learn RXJAVA for Android. Parts make sense and I'm still confused about a lot of the other bits but, given some time I hope it will all make a lot more sense.
At present I'm having trouble with the 'map' functionality. I'm receiving an error but cannot quite figure out how to resolve it.
Before I share my code, I'll explain my understanding..
At a simple level..
Observable - Code that emits data.
Observer - Code that processes the emitted data.
Map - Code that takes in data of type A and returns it processed or as type B.
So, with this in mind:
In gradle I have:
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
If I have:
//declaration at top of file
private Observable<Integer> myIntObservable;
private Observer<Integer> myIntObserver;
private Observer<String> myStringObserver;
private Observable<String> myStringObservable;
//usage in a function
myIntObserver = new Observer<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Integer value) {
Toast.makeText(getApplicationContext(), "" + value, Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Toast.makeText(getApplicationContext(), "Int Observer Async Complete", Toast.LENGTH_SHORT).show();
}
};
//Connect my Observable to the observer.
myIntObservable.observeOn(Schedulers.io());
myIntObservable.subscribeOn(AndroidSchedulers.mainThread());
myIntObservable.subscribe(myIntObserver);
This all works fine... my map usage is similar..
What I would like to do is use this same observable that returns an int, then use the map code to instead return a string...
Therefore:
myStringObservable
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.map(new Function<Integer, String>() {
#Override
public String apply(Integer query){
return "String Observable result == " + query;
}
});
Now, I have two issues:
a) The build error I receive is:
Error:(179, 17) error: method map in class Observable cannot be applied to given types;
required: Function
found: >
reason: cannot infer type-variable(s) R
(argument mismatch; > cannot be converted to Function)
where R,T are type-variables:
R extends Object declared in method map(Function)
T extends Object declared in class Observable
I believe that this is essentially telling me that the types are not correct for my usage but, I can't clearly see... how to resolve this.
b) The map code that I have posted above doesn't connect the observable to what it needs to observe... hence, should I add the subscribe line before the map command?
Therefore, I tried this..
public void setupAsyncSubscription(){
myIntObservable
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(myIntObserver)
.map(new Function<Integer, String>() {
#Override
public String apply(Integer query){
return "String Observable result == " + query;
}
});
}
Whilst this removes the error detailed in 'a' it instead provides me with the following error:
Error:(180, 17) error: void cannot be dereferenced (this points to the 'map' call)
Finally, I can also see that what I 'return' back from the 'map' function isn't being processed at all... I'm not clear how to process that. I feel that I should be using the .subscribe call in that case??
I 'think' that I am slowly on the right path to resolving the issue(s), but I'm not quite there and I don't want to just try and happen upon the answer without understanding what the problem is.
As always, any help is appreciated.
You have multiple issues here. There are - one by one:
//Connect my Observable to the observer.
myIntObservable.observeOn(Schedulers.io());
myIntObservable.subscribeOn(AndroidSchedulers.mainThread());
myIntObservable.subscribe(myIntObserver);
Code above would not work as you probably think.
Operators observeOn, subscribeOn are not designed to change internal observable state. They are returning new observable with desired behaviour.
To accomplish observing on io() thread and subscribing your observable on mainThread() you need to change the code:
//Connect my Observable to the observer.
myIntObservable = myIntObservable.observeOn(Schedulers.io());
myIntObservable = myIntObservable.subscribeOn(AndroidSchedulers.mainThread());
myIntObservable.subscribe(myIntObserver);
Or use (preferred) chaining:
//Connect my Observable to the observer.
myIntObservable
.observeOn(Schedulers.io());
.subscribeOn(AndroidSchedulers.mainThread());
.subscribe(myIntObserver);
For the code same as yours, calling .subscribe() on not changed Observable will result in subscribing and observing on the same thread from which you call .subscribe() (most likely from main thread).
Keep in mind you need to dispose observable once the work is finished.
When it comes to mapping problem - map() operator changes one type of Observable<A> into another type of observable Observable<B>.
If you'd like to end up with String objects converted from Integer objects you need to use - as a source of data - your original myIntObservable:
myStringObservable = myIntObservable
(...)
.map(new Function<Integer, String>() {
#Override
public String apply(Integer query){
return "String Observable result == " + query;
}
});
In above example myIntObservable will emit Integer objects (as expected in .apply(Integer query) method. Then .map() operator will create another Observable of type Observable<String> you can assign to myStringObservable (or do whatever you want from here).
Then, using myStringObservable you can subscribe to its events:
myStringObservable.subscribe(myStringObserver)
Again, please remember to dispose Observable when work is done.
Please also note that you should:
.observeOn() as soon as possible for current piece of work,
.subscribeOn() as late as possible (you don't want to continue io() or computation() operations on your main thread, right?).
Hint at the end: consider using lambdas with RxJava. Using all these anonymous classes (new Function() etc.) will make your code hard to read in the nearest future.
The first problem is that myStringObservable emits object of class String, but you're trying to map function which expects arguments of class Integer. In order to achieve what you want you should use myIntObservable.
The second problem is that subscribe call returns a Disposable object for you and you can't do map after that. Remove that subscribe call and this should be fine. Please also note that your method signature tells that it returns String but it can't return String, it can return Observable<String>.
I'm beginning with Java 8 and I was wondering if I can convert a loop with a try-catch clause, into a lambda function?
Below is the method code I would like to convert into:
for (File f: files) {
JSONOject obj;
try (FileWriter fw= new FileWriter("path.csv")) {
obj= (JSONObject) parser.parse(new FileWriter(f));
readOBJ(valueType,results,obj);
results.put(day, new JobClass(day,work, time,description));
Date d= results.keySet();
Calendar c= Calendar.getinstance();
c.setTime(d);
Map<Date, JobClass> daysList= new HashMap<>();
j.insertDaysList(results,c,fw,daysList);
results.putAll(daysList);
j.resSort(results,resDayList);
} catch (IOException ex) {
e.printStacktrace();
}
}
return resDaysList;
Assuming it's the files iteration you want to convert to a Stream, that can easily be done. At the moment, your loop contents don't even throw uncaught exceptions, so there's no difficulty here (though I would suggest refactoring it into its own method first).
As lambda expressions in Java are just a means to conveniently provide implementations of single-abstract-method (SAM) interfaces as anonymous objects and the standard Stream interface offers the forEach() method for internal iteration, you can encapsulate pretty much everything you want to within your expression.
forEach(Consumer<? super T> action) expects you to provide it with an object of the Consumer interface, which need only have one method implementation consuming an object of any kind and returning no value.
So you simply put the code within your loop into the expression (or, as already proposed, transfer it into it's own method first) and you're done. The only thing you'll need to think about is how to treat your return statement, as it's not possible to return values from within the forEach() method (due to it being a so called "terminal" method of return type void). But you'll be able to pass your list into the lambda expression, modify your values in any way you see fit and keep working with it after the lambda is done, without any problem.
The try-catch-block does not affect the lambda expression itself. After all, the following two segments of code are equivalent:
List<String> someList = Arrays.asList("example", "of", "lambda", "code");
// lambda style
someList.stream().forEach( item -> {
try {
System.out.println(item.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
// anonymous object style
someList.stream().forEach(new Consumer<String>() {
#Override
public void accept(String s) {
try {
System.out.println(item.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
});
I'm learning RxJava and, as my first experiment, trying to rewrite the code in the first run() method in this code (cited on Netflix's blog as a problem RxJava can help solve) to improve its asynchronicity using RxJava, i.e. so it doesn't wait for the result of the first Future (f1.get()) before proceeding on to the rest of the code.
f3 depends on f1. I see how to handle this, flatMap seems to do the trick:
Observable<String> f3Observable = Observable.from(executor.submit(new CallToRemoteServiceA()))
.flatMap(new Func1<String, Observable<String>>() {
#Override
public Observable<String> call(String s) {
return Observable.from(executor.submit(new CallToRemoteServiceC(s)));
}
});
Next, f4 and f5 depend on f2. I have this:
final Observable<Integer> f4And5Observable = Observable.from(executor.submit(new CallToRemoteServiceB()))
.flatMap(new Func1<Integer, Observable<Integer>>() {
#Override
public Observable<Integer> call(Integer i) {
Observable<Integer> f4Observable = Observable.from(executor.submit(new CallToRemoteServiceD(i)));
Observable<Integer> f5Observable = Observable.from(executor.submit(new CallToRemoteServiceE(i)));
return Observable.merge(f4Observable, f5Observable);
}
});
Which starts to get weird (mergeing them probably isn't what I want...) but allows me to do this at the end, not quite what I want:
f3Observable.subscribe(new Action1<String>() {
#Override
public void call(String s) {
System.out.println("Observed from f3: " + s);
f4And5Observable.subscribe(new Action1<Integer>() {
#Override
public void call(Integer i) {
System.out.println("Observed from f4 and f5: " + i);
}
});
}
});
That gives me:
Observed from f3: responseB_responseA
Observed from f4 and f5: 140
Observed from f4 and f5: 5100
which is all the numbers, but unfortunately I get the results in separate invocations, so I can't quite replace the final println in the original code:
System.out.println(f3.get() + " => " + (f4.get() * f5.get()));
I don't understand how to get access to both those return values on the same line. I think there's probably some functional programming fu I'm missing here. How can I do this? Thanks.
It looks like all you really need is a bit more encouragement and perspective on how RX is used. I'd suggest you read more into the documentation as well as marble diagrams (I know they're not always useful). I also suggest looking into the lift() function and operators.
The entire point of an observable is to concatenate data flow and data manipulation into a single object
The point of calls to map, flatMap and filter are to manipulate the data in your data flow
The point of merges are to combine data flows
The point of operators are to allow you to disrupt a steady stream of observables and define your own operations on a data flow. For example, I coded a moving average operator. That sums up n doubles in an Observable of doubles to return a stream of moving averages. The code literally looked like this
Observable movingAverage = Observable.from(mDoublesArray).lift(new MovingAverageOperator(frameSize))
You'll be a relieved that a lot of the filtering methods that you take for granted all have lift() under the hood.
With that said; all it takes to merge multiple dependencies is:
changing all incoming data to a standard data type using map or flatMap
merging standard data-types to a stream
using custom operators if one object needs to wait on another, or if you need to order data in the stream. Caution: this approach will slow the stream down
using to list or subscribe to collect all of that data
Edit: someone converted the following text, which I had added as an edit on the question, into an answer, which I appreciate, and understand may be the proper SO thing to do, however I do not consider this an answer because it's clearly not the right way to do it. I would not ever use this code nor would I advise anyone to copy it. Other/better solutions and comments welcome!
I was able to solve this with the following. I didn't realize you could flatMap an observable more than once, I assumed results could only be consumed once. So I just flatMap f2Observable twice (sorry, I renamed some stuff in the code since my original post), then zip on all the Observables, then subscribe to that. That Map in the zip to aggregate the values is undesirable because of the type juggling. Other/better solutions and comments welcome! The full code is viewable in a gist. Thank you.
Future<Integer> f2 = executor.submit(new CallToRemoteServiceB());
Observable<Integer> f2Observable = Observable.from(f2);
Observable<Integer> f4Observable = f2Observable
.flatMap(new Func1<Integer, Observable<Integer>>() {
#Override
public Observable<Integer> call(Integer integer) {
System.out.println("Observed from f2: " + integer);
Future<Integer> f4 = executor.submit(new CallToRemoteServiceD(integer));
return Observable.from(f4);
}
});
Observable<Integer> f5Observable = f2Observable
.flatMap(new Func1<Integer, Observable<Integer>>() {
#Override
public Observable<Integer> call(Integer integer) {
System.out.println("Observed from f2: " + integer);
Future<Integer> f5 = executor.submit(new CallToRemoteServiceE(integer));
return Observable.from(f5);
}
});
Observable.zip(f3Observable, f4Observable, f5Observable, new Func3<String, Integer, Integer, Map<String, String>>() {
#Override
public Map<String, String> call(String s, Integer integer, Integer integer2) {
Map<String, String> map = new HashMap<String, String>();
map.put("f3", s);
map.put("f4", String.valueOf(integer));
map.put("f5", String.valueOf(integer2));
return map;
}
}).subscribe(new Action1<Map<String, String>>() {
#Override
public void call(Map<String, String> map) {
System.out.println(map.get("f3") + " => " + (Integer.valueOf(map.get("f4")) * Integer.valueOf(map.get("f5"))));
}
});
And this yields me the desired output:
responseB_responseA => 714000
I think what you are looking for is switchmap. We ran into a similar issue where we have a session service that handles getting a new session from an api, and we need that session before we can get more data. We can add to the session observable that returns the sessionToken for use in our data call.
getSession returns an observable;
public getSession(): Observable<any>{
if (this.sessionToken)
return Observable.of(this.sessionToken);
else if(this.sessionObservable)
return this.sessionObservable;
else {
// simulate http call
this.sessionObservable = Observable.of(this.sessonTokenResponse)
.map(res => {
this.sessionObservable = null;
return res.headers["X-Session-Token"];
})
.delay(500)
.share();
return this.sessionObservable;
}
}
and getData takes that observable and appends to it.
public getData() {
if (this.dataObservable)
return this.dataObservable;
else {
this.dataObservable = this.sessionService.getSession()
.switchMap((sessionToken:string, index:number) =>{
//simulate data http call that needed sessionToken
return Observable.of(this.dataResponse)
.map(res => {
this.dataObservable = null;
return res.body;
})
.delay(1200)
})
.map ( data => {
return data;
})
.catch(err => {
console.log("err in data service", err);
// return err;
})
.share();
return this.dataObservable;
}
}
You will still need a flatmap to combine the not dependent observables.
Plunkr: http://plnkr.co/edit/hiA1jP?p=info
Where I got the idea to use switch map: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html