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();
Related
I have a chain of calls to internet, database and as result I show collected info to user. Now I have very ugly 3-level nested RxJava stream. I really want to make it smooth and easy to read, but I've stuck really hard.
I already read everything about Map, flatMap, zip, etc. Cant' make things work together.
Code: make api call. Received info put in database subscribing to another stream in onSuccess method of first stream, and in onSuccess method of second stream received from DB info finally shows up to user.
Dat Frankenstein:
disposables.add(modelManager.apiCall()
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver {
public void onSuccess(ApiResponse apiResponse) {
modelManager.storeInDatabase(apiResponse)
//level 1 nested stream:
disposables.add(modelManager.loadFromDatabas()
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver{
public void onSuccess(Data data) {
view.showData(data);
}
public void onError(Throwable e) {
}
}));
}
#Override
public void onError(Throwable e) {
}
}));
}
I already read everything about Map, flatMap, zip, etc. Cant' make things work together.
Well, you missed something about flatMap, because this is what it's for ;)
disposables.add(
modelManager.apiCall()
.subscribeOn(Schedulers.io())
.doOnSuccess((apiResponse) -> {
modelManager.storeInDatabase(apiResponse)
})
.flatMap((apiResponse) -> {
modelManager.loadFromDatabase()
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe((data) -> {
view.showData(data);
})
);
But if you use a reactive database layer like Room's LiveData<List<T>> support, then you can actually ditch the modelManager.loadFromDatabase() part.
flatMap means convert the result of one stream into another stream, it is likely what you want.
Because you have an Observable that emits a ApiResponse then you have another "source of Observables" that takes this ApiResponse and gives another Observable that you want to observe on.
So you may likely want something like that:
disposables.add(modelManager.apiCall()
.flatMap(apiResponse -> {
modelManager.storeInDatabase(apiResponse);
return modelManager.loadFromDatabas()
})
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver {
public void onSuccess(ApiResponse apiResponse) {
view.showData(data);
}
public void onError(Throwable e) {
}
})
I have this code :
getLocationObservable() // ---> async operation that fetches the location.
// Once location is found(or failed to find) it sends it to this filter :
.filter(location -> { // ---> I want to use this location in the the onNext in the end
after finishing some calculation here, I either return 'true' and continue
to the next observable which is a Retrofit server call, or simply
return 'false' and quit.
})
.flatMap(location -> getRetrofitServerCallObservable( location )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer<MyCustomResponse>() {
#Override
public void onSubscribe(Disposable d) {
_disposable = d;
}
#Override
public void onNext(MyCustomResponse response) {
// I want to be able to use the `location` object here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I want to be able to use the location object from line 3(first observable), in the "onNext" that is trigerred by the second observable.
I can't manage to work it out.. any help would be much appreciated.
Instead of
getRetrofitServerCallObservable( location )
you could map the result to be a Pair (from your favourite library) of the response and the location:
getRetrofitServerCallObservable( location ).map(response -> Pair.create(location, response))
Then, in your onNext, you'd be receiving Pair<Location,MyCustomResponse> instances.
If you don't want to use a Pair class, you could use Object[], but if you do, please don't tell me about it :P
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);
I'm new to RxJava. I would like to download some data for each TempoAccount entity from given collection and store it all in a map accountsWithProjects. When the code of last onNext(TempoAccount tempoAccount) is completed I'd like to call filterAccountsWithProjects(accountsWithProjects) method. Is there some simple way to achieve it?
private void getProjectsForEachTempoAccount(Collection<TempoAccount> tempoAccounts) {
final Map<TempoAccount, Collection<TempoProject>> accountsWithProjects =
new HashMap<>(tempoAccounts.size());
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts);
accountsObservable
.compose(ObservableUtils.applySchedulers())
.subscribe(new ObserverAdapter<TempoAccount>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(TempoAccount tempoAccount) {
jira.requestProjectsInfoForTempoAccount(String.valueOf(tempoAccount.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverAdapter<Collection<TempoProject>>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(Collection<TempoProject> projects) {
accountsWithProjects.put(tempoAccount, projects);
}
});
}
#Override
public void onCompleted() {
filterAccountsWithProjects(accountsWithProjects);
}
});
}
Problem: In the code above filterAccountsWithProjects(accountsWithProjects) is fired before all observables from onNext(TempoAccount tempoAccount) are completed.
Edit:
I want to create an Observable of such a type: Observable<Map<TempoAccount, Collection<TempoProject>>.
I have two observables given:
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts)
Observable<Collection<TempoProject>> projectsForAccountObservable = jira.requestProjectsInfoForTempoAccount(TempoAccount account)
So my questions is: can I connnect them somehow and create the map having these two observables.
You should use the flatMap() function on your original stream to do the things you are currently doing in the onNext(). Also, you don't need to filter the stream in onComplete(). You could use filter() on the stream itself and deal with the problem in a more "Reactive" way.
Here is an example:
accountsObservable
.compose(ObservableUtils.applySchedulers())
.map(tempoAccount -> new Pair<TempoAccount, Collection<TempoProject>>(tempoAccount, fetchInfoAccountForTempoAccount(tempoAccount)))
.filter(pair -> hasProjects(pair))
.toMap(pair -> pair.first(), pair -> pair.second)
.subscribe(...)
EDIT:
Updated the suggested answer - you get the TempoAccounts, then you map each account to a Pair of account and collection of TempoProjects. You filter the pairs to see if you have any projects and then you use toMap() to create your desired result. Be aware, that to get toMap() working, your observables have to call onComplete() when the end of the stream is reached.