I'm trying to migrate from using plain Retrofit to using the RxJava extension for retrofit in order to make chain of API calls on the background thread.
For example, I have an object called ModelGroup which has a list of ModelPerson objects. My goal is to do the following.
Send ModelGroup to the server and receive a response, which is an integer, representing the newly inserted ID, let's call it newGroupId.
For each ModelPerson in ModelGroup, set Person.groupId to newGroupId.
Send each person to the server.
If all ModelPerson objects from the ModelGroup were successfully updated with newGroupId then respond with onSuccess, otherwise onError.
My current solution can be seen below.
private void makeGroupInsert(ModelGroup modelGroup) {
int newGroupId = myApi.insertNewGroup(modelGroup.getName(), modelGroup.getRating())
.execute()
.body();
for (ModelPerson person : modelGroup.getPersons()) {
person.setGroupId(newGroupId);
String response = myApi.insertNewPerson(
person.getGroup_id(),
person.getFirst_Name(),
person.getLast_Name())
.execute()
.body();
if (!response.equals("success")) {
// One failed to update, send error to main thread.
}
}
// All succeeded, send success back to main thread.
}
Question
How can I achieve the same (or better) functionality using a RxJava + Retrofit solution?
EDIT 1
MyApi is defined below.
public interface MyApi {
#POST("insert_new_group")
Call<Integer> insertNewGroup(#Query("group_name") String groupName,
#Query("group_rating") int rating);
#POST("insert_new_person")
Call<String> insertNewPerson(#Query("group_id") int groupId,
#Query("first_name") String firstName,
#Query("last_name") String lastName);
}
First of all, you need to change Retrofit beans to use Observables. For example, it can look like the following line:
#POST("insert_new_group")
Observable<Integer> insertNewGroup(...
Then you can chain requests:
void updateData() {
myApi.insertNewGroup(modelGroup.getName(), modelGroup.getRating()) //Creating new group and getting its ID
.switchMap(this::setGroupIdAll) //Calling observable that will loop thru all persons and set their groupIDs
.subscribe(
(n) -> {/*you will get String after every 'insertNewPerson' run*/},
(e) -> {/*error handling*/}
);
}
Observable<String> setGroupIdAll(Integer id) {
return Observable.fromIterable(personsIterable) //personsIterable contains all your ModelPerson objects
.flatMap(this::updatePerson); //Call Observabl;e that will send updated person to the server
}
Observable<String> updatePerson(ModelPerson person) {
return myApi.insertNewPerson(
person.getGroup_id(),
person.getFirst_Name(),
person.getLast_Name());
}
Related
I have a data source service, which takes an observer as a parameter.
void subscribe(Consumer onEventConsumer);
I want to use flux as a response stream for RSocket.
How can I do this?
As I see it now, it should be something like
Flux<T> controllerMethod(RequestMessage mgs) {
var flux = Flux.empty();
dataSource.subscribe(event -> flux.push(event));
return flux;
}
But I have big doubts that it's a proper solution, and I'm new in the reactive approach, I don't know what methods I should use here?
As Simon already pointed out, this is what you use Flux.create for.
Take a look at the Getting Started Guide on projectreactor.io.
In short, you register a custom listener inside the lambda of the create method:
Flux<String> bridge = Flux.create(sink -> {
myEventProcessor.register(
new MyEventListener<String>() {
public void onDataChunk(List<String> chunk) {
for(String s : chunk) {
sink.next(s);
}
}
public void processComplete() {
sink.complete();
}
});
});
What you want to do is to pass the incoming elements on to a FluxSink, which will then publish those elements on the Flux.
this is a typical use case of Flux.create. you register an obsereer from inside the create lambda, which will pass the data it receives down to the provided FluxSink
Let's say I have an android app with a single activity that contains a button. When I click the button I'd like to make several requests to a rest API that return JSON response. Then I parse the response to a java object and persist it with Room. For the http requests I implemented a Volley request queue as singleton.
The requests are asynchronous and deliver their responses back to the UI thread. There I let Room persist the objects.
I send my http request like this:
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
// call the insert method
}
#Override
public void onRestError(int code, String errorMessage) {
// handle error
}
}
Since Room forces you to dispatch the queries to worker threads, I'm using RxJava to handle the task. So, for example my Insert method returns an ArrayList of the IDs of the inserted objects wrapped in a Single<ArrayList<Integer>>. Then I call the Insert method and subscribe to the result like this:
myDisposable = MyDatabase.getInstance().myDao()
.insert(myObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(idList -> {
Log.d(TAG, "IDs inserted: " + idList.toString());
}, Throwable::printStackTrace);
However, I want to chain multiple requests to the server and then get notified when all are complete and the DB insertions are ready in order to update the UI (e.g. display confirm message, disable the save button). I read numerous articles but nowhere I could find how to perform this apparently easy task. Basically what I want to achieve is:
// a some sort of container for all the observables I get from the database insertions
private Object aPoolOfObservables;
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
aPoolOfObservables.add(MyDatabase.getInstance().myDao()
.insert(myObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()));
}
}
// repeat this n-times more
...
aPoolOfObservables.subscribe(new Listener() {
#Override
public void onComplete() {
// update UI
}
});
Then perform this request multiple times and add the responses to the collection of Single<> (or Maybe<> or Flowable<>) responses and subscribe not to every single stream but to the collection, because I only care that all the operations are complete. Chaining them by firing a request in the onRestSuccess of the previous one seems like a pretty awful solution.
Do you know if there is a RxJava mechanism that allows this?
Is there any general approach/design pattern to handle this situation? I can think of numerous cases when you'd like to e.g. enable a button only after multiple requests have been performed and delivered results. How do you create and subscribe to such event in the context of RxJava? I haven't worked a lot with reactive data so any knowledge will be appreciated.
You can wrap each request in a Single<Pair<ArrayList<Integer>, String>> to store each JSON responses per request. Then, execute them all together with Single.zip(...)
private CompositeDisposable disposables;
private ArrayList<Single<Pair<ArrayList<Integer>, String>>> singles;
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
// kotlin syntax
singles.add(
MyDatabase.getInstance().myDao().insert(myObject)
.flatMap { ids: ArrayList<String> ->
// transform single to include JSON response
return#flatMap Single.just(Pair(ids, response))
}
);
}
}
// kotlin syntax
disposables.add(
// execute all singles
Single.zip(singles) {}.subscribe()
);
I am getting a Flowable from one method called getAlldata()
and this method will get the data from the server and then modify the data that been returned base on the data that had been stored in the DB.
So the process of this method goes like this:
getdata from the server
doOnNext for each item get the id
get the local data by id.
modify the current item
The problem is:
the result will be return before actully the modification of the data in doOnNext() since getting the local data from the DB is another observable.
Question
how can I delay stream until the other observable that is on doOnNext() completes?
The codes
private Flowable<List<MyModule>> getAlldata() {
return remoteDataSource
.getData().flatMap(data -> Flowable.fromIterable(data))
.doOnNext(new Consumer<MyModule>() {
#Override
public void accept(MyModule singleItem) throws Exception {
localDataSource.getData(singleItem.getId())
.firstElement().toFlowable()
.subscribe(new Consumer<Optional<MyModule>>() {
#Override
public void accept(Optional<MyModule> itemOptional) throws Exception {
if (itemOptional.isPresent()) {
// modify the item
}
}
});
}
})
.distinct()
.sorted(ProductsRepository.this::sortItems)
.toList().toFlowable();
}
Use flatmap operator instead of doOnNext() which get the singleItem as a parameter and throw an observable of the type localdatabase. so now you can map the response instead of making your Observable wait.
Observable<ModuleData> obs = remoteDataSource
.getData().flatMapIterable(data ->data)
.flatMap(singleItem->localDataSource.getData(singleItem.getId()))
.distinct()
.sorted(ProductsRepository.this::sortItems)
.toList().toFlowable();
I have a scenario in which I've to bridge the nonreactive code with Reactive Code.
Consider the following scenario.
I have a list of 3 URLs in an ArrayList. I want to call each URL in the order they are inside the ArrayList. I can call only 1 URL at a time. If the first URL returns a successful Response, I want to call onComplete() and don't wanna execute the remaining URL. However, if the response is an error, I want to execute the next URL in the list. I don't want RxJava to call flatMap for the next URL unless I get an error response for the previous URL. Due to my primitive understanding of RxJava, I couldn't figure out a way to achieve this.
What I planned to do something like this:
Observable.fromIteratable(urlList)
.subscribeOn(Schedulars.io())
.flatMap(new Func(String url, String data) {
SomeNetworkLibrary.getData(url)
.OnResponse(new NewResponse() {
public void onSuccess(String dataFromInternet) {return dataFromInternet;}
public void onError(String errorMessage) {return errorMessage;)
})
// wait until we have response from the network call above and then return
// I don't know what will be the cleanest and efficient way of waiting here.
});
TLDR;
I don't want flatMap() to be called before the results from the previous flatMap() have been returned.
How can I do that?
You can turn the network api call into an Observable and then use take(1) after the flattening:
Observable.fromIteratable(urlList)
.subscribeOn(Schedulars.io())
.concatMapDelayError((String url, String data) -> {
return Observable.create(emitter -> {
SomeNetworkLibrary.getData(url)
.OnResponse(new NewResponse() {
public void onSuccess(String dataFromInternet) {
emitter.onNext(dataFromInternet);
// don't call emitter.onComplete() so that
// concatMapDelayError doesn't switch to the next source
}
public void onError(String errorMessage) {
emitter.onError(errorMessage);
}
);
});
// wait until we have response from the network call above and then return
// I don't know what will be the cleanest and efficient way of waiting here.
})
.take(1);
I'm trying to combine two forms insertion in one using RxJava, RxAndroid and Mosby3, but I can't find a way to make it work.
My structure:
public final class CheckinIntent {
private final CheckinCommand checkinCommand;
private final Bitmap signature;
public CheckinIntent(CheckinCommand checkinCommand, Bitmap signature) {
this.checkinCommand = checkinCommand;
this.signature = signature;
}
public CheckinCommand getCheckinCommand() {
return checkinCommand;
}
public Bitmap getSignature() {
return signature;
}
}
Where I fire my intent (MVI pattern):
final Observable<Bitmap> signatureObservable = Observable.just(BitmapFactory.decodeFile(storage.getFile("signs", booking.getBookingId()).getAbsolutePath()));
final Observable<CheckinCommand> checkinCommandObservable = Observable.just(new CheckinCommand(booking.getBookingId(), booking.getUserId(), booking.getPartnerId(), userDetailsTextView.getText().toString(), "google.com"));
final Observable<CheckinIntent> intentObservable = Observable.zip(signatureObservable, checkinCommandObservable, (image, command) -> new CheckinIntent(command, image));
return saveButtonClickObservable
.flatMap(bla -> intentObservable);
And binding it all together:
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> imageRepository.uploadImage(checkinIntent.getSignature())
.flatMap(command -> bookingRepository.doCheckin(command) <------ PROBLEM HERE, HOW CAN I ACCESS THE COMMAND FROM ABOVE ??
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Observable<CnhImageResponse> uploadImage(Bitmap bitmap);
My problem is, my uploadImage returns an internal structure that ends of on a String, but, how can I get the returned string, add it to my command object (setting the returned URL in this object) and continue the flow (sending my command to the cloud) ?
Thanks!
Just flatMap on the observable directly within the first flatMap. In that case you have reference to both, the checkinIntent and command
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> {
return imageRepository.uploadImage(checkinIntent.getSignature()
.flatMap(imageResponse -> bookingRepository.doCheckin(command) <-- Now you have access to both, command and CnhImageResponse
})
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Alternative solution: Pass a Pair<CheckinIntent, Command> to the Observable from bookingRepository.doCheckin(...) like this:
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> imageRepository.uploadImage(checkinIntent.getSignature()
.map(imageResponse -> Pair.create(checkinIntent, imageResponse))) // Returns a Pair<CheckinIntent, CnhImageResponse>
.flatMap(pair -> bookingRepository.doCheckin(pair.first) <-- Now you can access the pair holding both information
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Just a few other notes:
You almost ever want to prefer switchMap() over flatMap() in MVI. switchMap unsubscribes previous subscription while flatMap doesnt. That means that if you flatMap as you did in your code snipped and if for whatever reason a new checkinIntent is fired while the old one hasn't completed yet (i.e. imageRepository.uploadImage() is still in progress) you end up having two streams that will call CheckinView::render because the first one still continue to work and emit results down through your established observable stream. switchMap() prevents this by unsubscribing the first (uncompleted) intent before starting "switchMaping" the new intent so that you only have 1 stream at the time.
The way you build your CheckinIntent should be moved to the Presenter. This is kind of too much "logic" for a "dump" View. Also Observable.just(BitmapFactory.decodeFile(...)) is running on the main thread. I recommend to use Observable.fromCallable( () -> BitmapFactory.decodeFile(...)) as the later deferres his "work" (bitmap decoding) until this observable is actually subscribed and then you can apply background Schedulers. Observable.just() is basically the same as:
Bitmap bitmap = BitmapFactory.decodeFile(...); // Here is the "hard work" already done, even if observable below is not subscribed at all.
Observable.just(bitmap);