I'm very new to RxJava. I have a problem with converting a nested async operation into RxJava structure. Having a single async task that fetches data from the server has not been a problem to create, however I do have a problem with an exemplary case of this sort:
List<A> aaa = new ArrayList<>();
List<B> bbb = new ArrayList<>();
new FetchItemA(String id){
#Override
protected void onPostExecute(List<A> items){
foreach(A item:items){
new FetchItemB(item.getId())
#Override
protected void onPostExecute(List<B> newItems){
neededList.addAll(newItems);
}
}
}
}
}
The problem is with the return types. I've created my observable this way:
Observable.fromArray(String userId)
.map(new Function(String, List<A>){
#Override
public List<A> apply(String id){
return getListA();
}
})
.map(new Function<String, List<B>){
#Override
public List<B> apply(String id){
someList.add(getItemB(id));
return someList;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer(){
#Override
public void onCompleted(List<B> items){
bbb.addAll(items);
adapter.setItems(bbb);
}
});
This however is illegal as this Observableexpects the type List<A>whereas I'm returning List<B>. How can I structure my RxJava observable to be able to fetch a list of items (of type A, each having a unique id), and based on the fetched list, fetch a single item (of type B) with id (of each item of type A in the first list) as the argument, and only after add the received items (of type B) to a list?
The essential step in fetching the data is to transform each A into a B using a network call. In the RxJava world, that means wrapping the network call in an observable and then using the flatMap() operator.
Observable.fromList(aaa)
.flatMap(new Func1<A, Observable<B>>() {
#Override
public Observable<B> call(A a) {
return Observable.fromCallable(getNetworkValueAsB(item));
}
} )
.toList()
.subscribe( new Observer<List<B>>() {
#Override
public void onNext(B bItemList) {
bbb.addAll(bItemList);
adapter.setItems(bbb);
}
});
The operations are fromList() which converts from the List<A> to Observable<A>, flatMap() which converts from A to B using the call, and toList() which gathers all the B values produced into a list, which is then used in the subscription.
The fromList() operator might be called fromIterable() or from() depending on the version of the library you are using.
Edited to removed lambdas
Related
I have the following method:
public void caller(){
List<Class1> data1 = Arrays.asList(new Class1(), new Class1() ...);
List<Class2> data2 = Arrays.asList(new Class2(), new Class2() ...);
// The following is what I'm trying to implement:
List<BiConsumer<Class1, Double>> peeks1 = Arrays.asList(Class1::setOneNum, Class1::setAnotherNum, Class1:: setMoreNum);
List<BiConsumer<Class2, Double>> peeks2 = Arrays.asList(Class2::setSomeNum1, Class2::setSomeNum2);
helper(data1, peeks1);
helper(data2, peeks2);
...
}
private <T> List<T> helper(List<T> data, List<BiConsumer<T, Double>> peeks) {
for(BiConsumer<T, Double> singlePeek: peeks){
data = data.stream()
.peek(a -> singlePeek.accept(a, math.random()))
.collect(Collectors.toList());
}
return data;
}
There are other implementation in common for Class1 and Class2, the only difference are the methods called after the .stream() which is why I'm trying to "merge" the functions into one helper.
Where BiConsumer is a setter. I want to call a list of setters after stream(). But I cannot input a list of functional interface into helper() (what I tried was Arrays.asList(Class1::setNum, Class1::setAnotherNum, Class1::setMoreNum) won't work as an input since Array.asList() only accepts Object). So is there any work-around? Thanks!
#user7 Thanks for pointing it out. I was careless but I've fixed the "typo". And added the caller function.
You have to specify the target type, when you call the .asList method:
Arrays.<BiConsumer<Object, Double>>asList(Class1::setOneNum, ...)
Update:
According to the updated code of the question the result of Arrays.asList is not directly handed over to the helper method, so no explicit typing is required.
The only possible reasons left why the code is not working are:
At least one of the methods (setOneNum, setSomeNum1, ...) has wrong parameters types
At least one of the methods is not static
Could I advise you in trying to make it a little bit more functional?
For your code consider the following helper, this one will make use of function as a first class citizen concept and make some High Order Functions:
private <T, V> Function<Supplier<T>, Supplier<T>> helper(Supplier<V> v,
BiConsumer<T, V> bc) {
return (Supplier<T> r) -> {
bc.accept(r.get(), v.get());
return r;
};
}
This helper function expects a Supplier of some value kind of value and a BiConsumer that will be your setter function. The returns is a function of Suppliers of the same class you are working with.
With that we can make something like a pipe operator of functional languages. Their premises is that the data should processed in a pipeline operation.
List<Class1> data1 = Arrays.asList(new Class1(), new Class1());
List<Class2> data2 = Arrays.asList(new Class2(), new Class2());
Supplier<Double> random = () -> Math.random();
This will be our data, you have the same array and now a Supplier with the random value you want.
Now lets compose our pipeline with andThem:
data1.stream()//
.forEach(data -> {
helper(random, Class1::setOneNum)//
.andThen(helper(random, Class1::setAnotherNum))//
.andThen(helper(random, Class1::setMoreNum))//
.apply(() -> data);
System.out.println(data.toString());
});
data2.stream()//
.forEach(data -> {
helper(random, Class2::setSomeNum1)//
.andThen(helper(random, Class2::setSomeNum2))//
.apply(() -> data);
System.out.println(data.toString());
});
As you can see the helper function can be chained together with "andThem" method of Function interface. This will make Java execute the helper function and use it's return as the parameter of the next Function.
The data parameter will hole the values of classes and will be changed each chain. As we iterated all objects will
And the result:
Class1 [oneNum=0,047, anotherNum=0,482, moreNum=0,339]
Class1 [oneNum=0,131, anotherNum=0,889, moreNum=0,411]
Class2 [someNum1=0,18, someNum2=0,004]
Class2 [someNum1=0,497, someNum2=0,702]
I think it is the same result you want. And as you can see you don't need to pass any generics as the Java will understand it well.
The classes that I made for reference:
class Class1 {
double oneNum;
double anotherNum;
double moreNum;
public double getOneNum() {
return oneNum;
}
public void setOneNum(double oneNum) {
this.oneNum = oneNum;
}
public double getAnotherNum() {
return anotherNum;
}
public void setAnotherNum(double anotherNum) {
this.anotherNum = anotherNum;
}
public double getMoreNum() {
return moreNum;
}
public void setMoreNum(double moreNum) {
this.moreNum = moreNum;
}
#Override
public String toString() {
return MessageFormat.format("Class1 [oneNum={0}, anotherNum={1}, moreNum={2}]", oneNum, anotherNum, moreNum);
}
}
class Class2 {
double someNum1;
double someNum2;
public double getSomeNum1() {
return someNum1;
}
public void setSomeNum1(double someNum1) {
this.someNum1 = someNum1;
}
public double getSomeNum2() {
return someNum2;
}
public void setSomeNum2(double someNum2) {
this.someNum2 = someNum2;
}
#Override
public String toString() {
return MessageFormat.format("Class2 [someNum1={0}, someNum2={1}]", someNum1, someNum2);
}
}
I have these following scenarios -
Say I have 2 async callback(imagine I am calling 2 diff apis) methods callback1(Data d1) and callback2(Data d2), based on d1 and d2 (i.e when both the callback methods are being called)I have to call a method say setupUI(), How to efficiently do that using RxJava
There are two viewpagers v1 and v2 which needs to be synced, i.e on v1 pagechange v2 will also change its current page (indices will be the same) and vice-versa, using Rxjava
Try putting subjects in the callbacks to convert them into rx streams.
You can then zip the two subjects up and subscribe to the zipped observable to get the result of both callbacks at the same time
Example: lets make two subjects
PublishSubject<Data> subject1 = PublishSubject.create();
PublishSubject<Data> subject2 = PublishSubject.create();
We can use these to convert our callbacks into something we can subscribe to like this:
public void callback1(Data d1) {
subject1.onNext(d1);
}
public void callback2(Data d2) {
subject2.onNext(d2);
}
Now we can get the output when they both emit something like this:
class DataDto {
Data data1;
Data data2;
DataDto(Data data1, Data data2) {
this.data1 = data1;
this.data2 = data2;
}
}
public void main() {
Observable.zip(subject1, subject2, new BiFunction<Data, Data, DataDto>() {
#Override
public DataDto apply(#NonNull Data data1, #NonNull Data data2) throws Exception {
return new DataDto(data1, data2);
}
}).subscribe(new Consumer<DataDto>() {
#Override
public void accept(#NonNull DataDto dataDto) throws Exception {
//do something
}
});
}
What zip does is that it waits until both streams have emitted, then emits that as one item.
Here we made a DataDto which contains both data1 and data2
Hope this helps
I'm currently trying to hit a service and get returned a list of objects, before it gets returned to the subscriber, I want to make another synchronous call for each object in the list to make another service call to set a missing field. I'm successfully having all calls being made, but the object returned in the subscriber has this field I need to have set to null. Here is an example of my code:
Example Service:
rx.Observable<List<ExampleObject>> getExampleObject();
rx.Observable<MissingObject> getMissingObjectByFoo(#Path("foo") String foo);
Example Class:
public class ExampleObject {
String foo;
MissingObject bar;
public String getFoo() {
return this.foo;
}
public void setFoo(String value) {
this.foo = value;
}
public MissingObject getBar() {
return this.bar;
}
public void setBar(MissingObject value) {
this.bar = value;
}
}
Example Implementation:
mService.getExampleObject().flatMap(new Func1<List<ExampleObject>, Observable<?>>() {
#Override
public Observable<List<ExampleObject>> call(List<ExampleObject> exampleObjects) {
for (ExampleObject entry : exampleObjects) {
String foo = entry.getFoo();
mService.getMissingObjectByFoo(foo)
.subscribeOn(mScheduler.backgroundThread())
.observeOn(mScheduler.mainThread())
.subscribe(new Subscriber<MissingObject>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(MissingObject missingObject) {
entry.setBar(missingObject);
}
});
}
return Observable.just(exampleObjects);
};
Because your intermediary call to update the entry is asynchronous, I don't think you can stick to using a List<ExampleObject>, but should instead manipulate ExampleObject directly from the Observable:
mService.getExampleObject()
// Spread the list
.flatMap(list -> Observable.from(list))
// Update your object
// Here we zip the object with the missing object,
// so that when the missing object is obtained,
// we update the entry and emit it.
.flatMap(entry -> Observable.zip(
Observable.just(entry),
mDocsService.getMissingObjectByFoo(entry.getFoo()),
(entry, missingObject) -> {
entry.setBar(missingObject);
return entry;
})
)
// if you really want a map after all
.toList();
Side note:
You can skip the zip if you are fine with having the function in the map depend on an external variable (the entry). That's something I try to avoid, but here it is anyway:
.flatMap(entry -> mDocsService.getMissingObjectByFoo(entry.getFoo())
.map(missingObject -> {
entry.setBar(missingObject);
return entry;
})
)
You're looking for the zip operator, as described here: Zip Operator. I think you want to flatmap to a zip of all of your calls, so, something like this:
mService.getExampleObject().flatMap(new Func1<List<ExampleObject>, Observable<ExampleObject>>() {
#Override
public Observable<List<ExampleObject>> call(List<ExampleObject> exampleObjects) {
List<Observable<ExampleObject>> allTheObservables = new ArrayList<Observable<ExampleObject>>();
for (ExampleObject entry : exampleObjects) {
allTheObservables.add(mService.getMissingObjectByFoo(foo).map(new Func1<MissingObject, ExampleObject>() {
#Override
public ExampleObject call(MissingObject missingObject) {
return entry.setBar(missingObject);
}
}));
}
return Observable.zip(allTheObservables, new FuncN<ExampleObject>() {
#Override
public ExampleObject call(ExampleObject... args) {
return Arrays.asList(args);
}
});
}
});
and in case that doesn't work, or there are syntax issues, here's a concrete example, using the github api:
service.getContributorsObservable("square", "dagger")
.flatMap(new Func1<List<Contributor>, Observable<List<String>>>() {
#Override
public Observable<List<String>> call(List<Contributor> contributors) {
List<Observable<String>> allTheObservables = new ArrayList<>(contributors.size());
for (final Contributor contributor : contributors) {
allTheObservables.add(service.getContributorsObservable(contributor.login).map(new Func1<User, String>() {
#Override
public String call(User user) {
return contributor.login + " is " + user.name;
}
}));
}
return Observable.zip(allTheObservables, new FuncN<List<String>>() {
#Override
public List<String> call(Object... args) {
return Arrays.asList((String[]) args);
}
});
}
});
Keep in mind that this will make n+1 network calls, 1 for the list of ExampleObjects, and then 1 per ExampleObject in that list. If it is at all possible, I strongly suggest that you speak with the maintainer of the API to get the information lookup taken care of on the API side. Just know that this is going to use some bandwidth!
I have three Observables which I combine with combineLastest:
Observable<String> o1 = Observable.just("1");
Observable<String> o2 = Observable.just("2");
Observable<String> o3 = Observable.just("3");
Observable.combineLatest(o1, o2, o3, new Func3<String, String, String, Object>() {
#Override
public Object call(String s, String s2, String s3) {
return null;
}
});
I want to be notified about the first emission of one of the Observables without ignoring the later emissions, which I guess first operator would do. Is there a convenient operator for that like (example):
o1.doOnFirst(new Func1<String, Void>() {
#Override
public Void call(String s) {
return null;
}
})
I think you can have a practical doOnFirst with a simple take if you're handling a stream:
public static <T> Observable<T> withDoOnFirst(Observable<T> source, Action1<T> action) {
return source.take(1).doOnNext(action).concatWith(source);
}
This way the action is only bound to the first item.
This could be changed to handle observables which are not backed by streams adding skip to skip the already taken items:
public static <T> Observable<T> withDoOnFirstNonStream(Observable<T> source, Action1<T> action) {
return source.take(1).doOnNext(action).concatWith(source.skip(1));
}
For convenience, I created these extension functions for Flowable and Observable.
Note, that with doOnFirst() the action will be called before the first element emission, whilst doAfterFirst() will firstly emit the first item and then perform the action.
fun <T> Observable<T>.doOnFirst(onFirstAction: (T) -> Unit): Observable<T> =
take(1)
.doOnNext { onFirstAction.invoke(it) }
.concatWith(skip(1))
fun <T> Flowable<T>.doOnFirst(onFirstAction: (T) -> Unit): Flowable<T> =
take(1)
.doOnNext { onFirstAction.invoke(it) }
.concatWith(skip(1))
fun <T> Observable<T>.doAfterFirst(afterFirstAction: (T) -> Unit): Observable<T> =
take(1)
.doAfterNext { afterFirstAction.invoke(it) }
.concatWith(skip(1))
fun <T> Flowable<T>.doAfterFirst(afterFirstAction: (T) -> Unit): Flowable<T> =
take(1)
.doAfterNext { afterFirstAction.invoke(it) }
.concatWith(skip(1))
Usage is as simple as this:
Flowable.fromArray(1, 2, 3)
.doOnFirst { System.err.println("First $it") }
.subscribe { println(it) }
Output:
// First 1
// 1
// 2
// 3
And:
Flowable.fromArray(1, 2, 3)
.doAfterFirst { System.err.println("First $it") }
.subscribe { println(it) }
Output:
// 1
// First 1
// 2
// 3
There are a couple of solutions I can think of.
The first one is an ugly but simple hack of doOnNext. Just add a boolean field to the Action1 indicating whether the first item has been received. Once received, do whatever it is you want to do, and flip the boolean. For example:
Observable.just("1").doOnNext(new Action1<String>() {
boolean first = true;
#Override
public void call(String t) {
if (first) {
// Do soemthing
first = false;
}
}
});
The second one is to subscribe twice on the observable you want to monitor using publish or share(), with one of those publications going through first (depending on whether you want to manually connect to the published observable). You'll end up with two separate observables that emit the same items, only the first one will stop after the first emission:
ConnectableObservable<String> o1 = Observable.just("1").publish();
o1.first().subscribe(System.out::println); //Subscirbed only to the first item
o1.subscribe(System.out::println); //Subscirbed to all items
o1.connect(); //Connect both subscribers
Using rxjava-extras:
observable
.compose(Transformers.doOnFirst(System.out::println))
It's unit tested and under the covers just uses a per-subscription counter in an operator. Note that per-subscription is important as there are plenty of uses cases where an observable instance gets used more than once and we want the doOnFirst operator to apply each time.
Source code is here.
I'm using RxJava to essentially collect the list of individually emitted Observables and combine them into a list of Observables (essentially sort of the opposite of flatMap). Here's my code:
// myEvent.findMemberships() returns an Observable<List<Membership>>
myEvent.findMemberships()
.flatMap(new Func1<List<Membership>, Observable<User>>() {
#Override
public Observable<User> call(List<Membership> memberships) {
List<User> users = new ArrayList<User>();
for (Membership membership : memberships) {
users.add(membership.getUser());
}
return Observable.from(users);
}
})
.toList()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<User>>() {
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) {
Timber.e(e, "Error when trying to get memberships");
}
#Override
public void onNext(List<User> users) {
Timber.d("%d users emitted", users.size());
}
})
I notice that my onNext is never called. I can't seem to understand this. If i remove the .toList call and basically output the individual Users (as shown below) it works by emitting each item.
subscriptions //
.add(currentEvent.findMemberships()
.flatMap(new Func1<List<Membership>, Observable<User>>() {
#Override
public Observable<User> call(List<Membership> memberships) {
List<User> users = new ArrayList<User>();
for (Membership membership : memberships) {
users.add(membership.getUser());
}
return Observable.from(users);
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) {
Timber.e(e, "Error when trying to get memberships");
}
#Override
public void onNext(User user) {
Timber.d("%d users emitted", user.getName());
}
}));
Q1. Is my understanding of .toList incorrect?
Q2. How does one combing a stream of individually emitted Observable<Object>s into a single Observable<List<Object>> ?
** EDIT
#kjones totally nailed the issue. I was not calling onComplete with my findMemberships call. I've added the code snippet below. My real use case was a little more convoluted with a bunch of more transformations which is why I needed to be using .toList call. As #zsxwing also rightly pointed out, for just this use case, a simple map will suffice.
public Observable<List<Membership>> findMemberships() {
return Observable.create(new Observable.OnSubscribe<List<Membership>>() {
#Override
public void call(Subscriber<? super List<Membership>> subscriber) {
try {
// .....
List<Membership> memberships = queryMyDb();
subscriber.onNext(memberships);
// BELOW STATEMENT FIXES THE PROBLEM ><
// subscriber.onCompleted();
} catch (SQLException e) {
// ...
}
}
});
}
It looks like the Observable returned by the myEvent.findMemberships() call is never calling onComplete. Can you show this code?
If that is the case, it would explain the behavior you are seeing. The .toList() will not emit the list until all items have been emitted (signaled by onComplete).
Your second version without .toList(), would proceed as follows:
.findMemberships()
emits a single List<Membership>
.flatMap()
transforms List<Membership> into a single List<User>
Observable.from(users) creates an observable that emits each user
.subscribe()
onNext() is called for each user
onCompleted() is never called.
Your original version:
.findMemberships()
emits a single List<Membership>
.flatMap()
transforms List<Membership> into a single List<User>
Observable.from(users) creates an observable that emits each user
.toList()
buffers each User waiting for onCompleted() to be called
onCompleted is never called because the .findMemberships Observable never completes
There are several solutions:
1) Make the findMemberShips() Observable call onComplete.This may not be desirable if the Observable returned by findMemberShips() is a Rx Subject (PublishSubject, BehaviorSubject, etc)
2) Use Observable.just() instead of Observable.from(). You already have a List<User> in .flatMap(), just return it. Using Observable.from(users) creates an Observable that emits each user. Observable.just(users) would create an Observable that emits a single List<User>. No need for .toList().
3) Use .map() instead of .flatMap(). Again, no need for .toList(). Since each List<Membership> gets transformed into a List<User> you only need to use .map().
myEvent
.findMemberships()
.map(new Func1<List<Membership>, List<User>>() {
#Override
public List<User> call(List<Membership> memberships) {
List<User> users = new ArrayList<User>();
for (Membership membership : memberships) {
users.add(membership.getUser());
}
return users;
}
})