I want to perform the following. I have a list of transactions which i want to update by making 2 api requests (i am using retrofit2), for each transactions and then saving the result into a database(using the observer). After some searching i decided to use zip operator to combine the 2 requests but the issue that i'm have is that i cannot identify when the whole process is finished to update the UI. Code looks like this.
for (Transaction realmTransaction : allTransactions) {
Observable<Map<String, String>> obs1 = getObs1(realmTransaction);
Observable<Map<String, String>> obs2= getObs2(realmTransaction);
Observable.zip(obs1, obs2,
(map1, map2) -> {
Map<String, String> combined = new HashMap<>();
// do some processing and return a single map after
return combined;
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getObserver(realmTransaction));
}
public Observer<Map<String, String>> getObserver(Transaction t){
return new Observer<Map<String, String>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Map<String, String> stringStringMap) {
// update database
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}
}
The observer that i have updates a field of the realmTransaction object.
My question is how do i get notified when the for loop has ended??
I would like to sent an event (maybe use EventBust) after the whole process has finish to kick off some other method.
Thanks
Also another small question that i have is about the function that i provide inside the zip operator, how can i specify on which thread that function will run on? I would like to use a computation thread for that thats why i put observeOn twice, but I couldnt find an answer anywhere
Whenever you have a for loop, you should think about range, fromArray or fromIterable. In addition, you may not need the full subscribe but doOnNext():
Observable.fromIterable(allTransactions)
.flatMap(realmTransaction -> {
Observable<Map<String, String>> obs1 = getObs1(realmTransaction);
Observable<Map<String, String>> obs2= getObs2(realmTransaction);
return Observable.zip(obs1, obs2, (map1, map2) -> {
Map<String, String> combined = new HashMap<>();
// do some processing and return a single map after
return combined;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(stringStringMap -> handle(stringStringMap, realmTransaction));
})
.ignoreElements()
.subscribe(() -> handleCompleted(), e -> handleError(e));
Since every thing is being done asynchronously , you can create a local variable outside the loop,say count and and keep incrementing it getObserver function and later check the count is equal to the length of allTransactions.
And about your second question, I think the subscribe thread will be used. You using the 2 subscribeOn, only last one will be used.
Related
I have posted all methods they are working separately , but I face issues with the first one, where I concatWith() two flowables
return userFavouriteStores()
.concatWith(userOtherStores())
.doOnNext(new Consumer<List<StoreModel>>() {
#Override
public void accept(#io.reactivex.annotations.NonNull List<StoreModel> storeModels) throws Exception {
Log.i("storeModels", "" + storeModels);
}
})
public Flowable<List<StoreModel>> userFavouriteStores() {
return userStores()
.map(UserStores::favoriteStores)
.flatMap(storeId -> storeDao.storesWithIds(storeId))
.map(stores -> { // TODO Konvert to Kotlin map {}
List<StoreModel> result = new ArrayList<>(stores.size());
for (se.ica.handla.repositories.local.Store store : stores) {
result.add(store.toStoreModel(StoreModel.Source.Favourite));
}
return result;
}); }
public Flowable<List<StoreModel>> userOtherStores() {
return userStores().map(UserStores::otherStores)
.flatMap(storeId -> storeDao.storesWithIds(storeId))
.map(stores -> {
List<StoreModel> result = new ArrayList<>(stores.size());
for (Store store : stores) {
result.add(store.toStoreModel(StoreModel.Source.Other));
}
return result;
});}
updated method :userStores() is used for favorite and other stores ,
private Flowable<UserStores> userStores() {
return apiIcaSeResource
.userStores()
.toFlowable(); }
#GET("user/stores")
Single<UserStores> userStores();
Following the comments follow up, and additional information, you don't have a problem specifically with the concat(), I'm assuming it is work, it's just not the tool for what you want to achieve here.
concat() will not concatenate two lists to a single list, but rathe will first emit all items by first Flowable and only then items emitted by second Flowable (hence you must have onComplete so concat will know when Flowable is end, what I asked in the begining).
in order to combine the lists together, I would suggest to zip both stores Obesrvables (favorites/ others), and then simply combine to list to have single output of combined list.
Besides that, as you pointed out, as both stores Observables comes from userStores(), you will invoke the network request twice, which definitely not necessary. you can solve it using publish(), that will share and multicast the network result to both Observables, resulting with single network request.
to sum it up, I would rather recommend to use Single here, not Flowable as you don't have backpressure consecrations. something like the following implementation:
Observable<List<StoreModel>> publish = userStores()
.toObservable()
.publish(userStores ->
Single.zip(
userFavouriteStores(userStores.singleOrError()),
userOtherStores(userStores.singleOrError()),
(favoriteStores, otherStores) -> {
favoriteStores.addAll(otherStores);
return favoriteStores;
}
)
.toObservable()
);
I need to iterate through a list of data, get all their Ids, trigger network calls using those Ids, and then do something once I get the list of results (The server could take list of Ids and return a list of result but it doesn't work that way as of now).
Currently I got it working like this:
for (Data data: dataList) {
String id = data.getId();
idObservables.add(dataService.getResultFromNetwork(id));
}
Observable.zip(idObservables, new FuncN<List<Result>>() {
#Override
public List<Result> call(Object... args) {
List<Result> resultList = new ArrayList<>();
for (Object arg : args) {
resultList.add((Result) arg));
}
return resultList;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Result>>() {
#Override
public void call(List<Result> resultList) {
// Do something with the list of Result
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.e("", "error", throwable);
}
});
But obviously I'm not happy with the way it's done. It will be great to know better ways to handle a case like this using RxJava in Android.
Cheers!!
Apologies for the lambda-isms, but it really makes the logic easier to read:
Observable
.fromIterable(dataList)
.flatMap(data ->
dataService
.getResultFromNetwork(data.getId())
.subscribeOn(Schedulers.io())
)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(list -> {
// do something
});
The idea is to keep as much as the pipeline in Rx-land; it's worth it to have simple methods taking normal parameters and return observables, and complex methods take observables and return observables.
Note: the above will not retain ordering; if you need an ordered list, use concatMap with prefetch.
I get a List of searchItems. For each element in this list exists a Lat und Lng. For these coordinates I use a googleService that takes these two values and returns a JsonObject with the name (city name) for this location. This works fine! In my onNext I can see in my log output the city.
Now my problem: I want to store this name in the corresponding list element. like: item.setLocation(loc) -> but I can not set access to the item in onNext() ! how can I get access to item ??
Observable.from(searchItems)
.flatMap(item -> googleService.getCityNameFromLatLngObserable(item.getLat(), item.getLng(), null)
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<JsonObject>() {
#Override
public void onCompleted() {
view.updateSearches(searchItem);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(JsonObject response) {
if (response.get("results").getAsJsonArray().size() > 0) {
String loc = response.get("results").getAsJsonArray()
.get(0).getAsJsonObject().get("address_components").getAsJsonArray()
.get(2).getAsJsonObject().get("long_name").toString();
Log.d("CONAN", "City: "+loc);
item.setLocation(loc); //does not work
}
}
});
Actually you shouldn't really change your searchItems but rather create new ones with the new info. This is to enforce immutability, which is very dear to Rx.
There is more than one way to do this, but here's one possible way. You need the item to call your Google service so you cannot use zip or any of its friends. So let's keep the first bit like you have it and get an observable emitting each search item:
Observable.from(searchItems)
Now let's go on and create new search items with their location.
Observable.from(searchItems)
.flatMap(item -> googleService
.getCityNameFromLatLngObserable(
item.getLat(),
item.getLng(), null)
.map(jsonObject -> /* create a
new item with location */))
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<JsonObject>() {
#Override
public void onCompleted() {
view.updateSearches(searchItem);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Item item) {
// Your items with location
}
});
What I did was map the results of the googleService. getCityNameFromLatLngObserable to an item that has the location from the result. Notice how here you have access to both the item and the json object return by google?
Now inside the map function you can either use a function such as createWithLocation(item, getLocation(jsonObject)) or use something like the builder pattern:
item.toBuilder()
.location(getLocation(jsonObject))
.build();
Notice also that on the subscriber onNext you have now an Item instead of a JsonObject and with the location filled. I assume you can code getLocation since your example already has this.
One last thing, you can also call the setter in the item instance and not create a new one. I would advise against this because you'll be adding a side effect to a call that others might not expect. It's usually a source for bugs. However, you can still do it :)
Hope it helps
Help in composing multiple network calls and accumulate the result in Rxjava. (I am using in an Android application.)
State
-- List<City> cityList;
City
- cityId;
RestCall 1
Observable<State> stateRequest = restService.getStates();
RestCall 2
Observable<CityDetail> cityRequest = restService.getCityDetail(cityId);
In UI i have to display list of cities after getting all the details of each city and then show in the listview.
How do i achieve the parllel network calls and accumulate the result. ?
I want all the city detail results to be put in List in source State 'object'. As state object has some information which need to be dislayed as well.Is this possible ?
stateRequest ???
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<State>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(State result) {
// Get city list and display
}
});
I checked this example which shows how we can zip more tha one observable response. Below snippet shows 3 observables combined.
But in my case i have to make 20 network calls parallel or sequential ( i mean in background but one after another). How do i achieve this. Any help or directions ?
https://gist.github.com/skehlet/9418379
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;
}
I think that your code can be simplified to something like this, as your use of the zip operator is close to the use of toList operator
stateRequest
.subscribe(State state -> {
Observable.from(state.getCityList())
.flatMap(City city -> restService.getCityDetail(city.getId())
.toList()
.subscribe(List<City> cities -> {
state.clear();
state.addAll(cities);
});
});
As RxJava doesn't provide a throttle operator, you may build something similar like this :
Observable<City> limiter = Observable.zip(Observable.interval(1, SECONDS), aCity, (i, c) -> c);
Using this, limiter is an observable that will emit a city each second.
So, with your code, if you want to limit call to getCityDetail for example :
Observable<Object> limiter = Observable.interval(1, SECONDS);
stateRequest
.subscribe(State state -> {
Observable.zip(limiter, Observable.from(state.getCityList()), (i, c) -> c)
.flatMap(City city -> restService.getCityDetail(city.getId())
.toList()
.subscribe(List<City> cities -> {
state.clear();
state.addAll(cities);
});
});
stateRequest
.flatMap(new Func1<State, Observable<State>>() {
#Override
public Observable<State> call(final State state) {
List<Observable> cityObservablesList = new ArrayList<Observable>();
for(City city: state.getCityList()) {
cityObservablesList.add(restService.getCityDetail(city.getId());
}
Observable cityObservables = Observable.from(cityObservablesList);
return Observables.zip(cityObservables, new FuncN<State>() {
#Override
public State call(Object... args) {
List<City> cityList = state.getCityList();
cityList.clear();
for(Object object: args) {
cityList.add((City)object);
}
return state;
}
})
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<State>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(State result) {
// Get city list and display
}
});
I got it working with the help of zip operator and Iterable for city list as first parameter.
But i face another issue. Since the zip executes the job in parallel, 10-15 network calls are executed in parallel and server rejecting with Maximum Query Per Second error (QPS - 403).
How do i instruct the zip operator to execute the tasks one after another ?
I did solve this issue by adding a delay [delay(c*200, TimeUnit.MILLISECONDS))] to city observable. But doesn't seem like a proper solution.
Any advise ?
Take a look at flatMap(Function.., BiFunction..). Maybe that's what you need.
statesRepository.getStates()
.flatMap(states-> Observable.fromIterable(states))
.flatMap(
state-> cityRepository.getStateCities(state),
(state, cityList) -> {
state.setCities(cityList);
return state;
})
.subscribe(state-> showStateWithCity(state));
I'm new to RxJava, here's my case,
send request A and will get List<A> back
for each A, send request AA and will get AA back, bind A and AA then
there is B & BB with similar logic
do something only after all requests complete
Example:
request(url1, callback(List<A> listA) {
for (A a : listA) {
request(url2, callback(AA aa) {
a.set(aa);
}
}
}
A and B are independent
How to structure the code? I also used Retrofit as network client.
OK, I think this should solve the first part of your problem:
Notice that the second call to flatMap is given 2 arguments - there is a version of flatMap that not only produces an Observable for each input item but that also take a second function which in turn will combine each item from the resulting Observable with the corresponding input item.
Have a look at the third graphic under this heading to get an intuitive understanding:
https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap-concatmap-and-flatmapiterable
Observable<A> obeservableOfAs = retrofitClient.getListOfAs()
.flatMap(new Func1<List<A>, Observable<A>>() {
#Override
public Observable<A> call(List<A> listOfAs) {
return Observable.from(listOfAs);
}
)}
.flatMap(new Func1<A, Observable<AA>>() {
#Override
public Observable<AA> call(A someA) {
return retrofitClient.getTheAaForMyA(someA);
}
},
new Func2<A, AA, A>() {
#Override
public A call(A someA, AA theAaforMyA) {
return someA.set(theAaforMyA);
}
})
...
From here on I am still not sure how you want to continue: Are you ready to just subscribe to the resulting Observable of As? That way you could handle each of the As (onNext) or just wait until all are done (onCompleted).
ADDENDUM: To collect all Items into a single List at the end, that is turn your Observable<A> into an Observable<List<A>> use toList().
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist
So you have:
Observable<List<A>> observableOfListOfAs = observableOfAs.toList();
If you need more fine grained control over the construction of your list, you can also use reduce.
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce
For the Bs, simply duplicate the whole flow you used for the As.
You can then use zip to wait for both flows to complete:
Observable.zip(
observableOfListOfAs,
observableOfListOfBs,
new Func2<List<A>, List<B>, MyPairOfLists>() {
#Override
public MyPairOfLists call(List<A> as, List<B> bs) {
return new MyPairOfLists(as, bs);
}
}
)
.subscribe(new Subscriber<MyPairOfLists>() {
// onError() and onCompleted() are omitted here
#Override
public void onNext(MyPairOfLists pair) {
// now both the as and the bs are ready to use:
List<A> as = pair.getAs();
List<B> bs = pair.getBs();
// do something here!
}
});
I suppose you can guess the definition of MyPairOfLists.