I've been trying to convert my onErrors into notifications in order to keep the stream emitting items. As far as I understood the materialize() operator does just that. So basically:
materialize() / dematerialize() are available to turn terminal events
into Notification
So I made a test for this based on this question (How to continue streaming items after error in RxJava?). I tried the following:
#Test
public void materializeTest() {
final Observable<String> stringObservable = Observable.fromArray("1", "2", "3")
.flatMap(x -> {
if (x.equals("2")) {
return Observable.error(new NullPointerException());
}
return Observable.just(x);
})
.materialize()
.map(n -> n.getValue());
final TestObserver<String> testObs = stringObservable.test();
Java6Assertions.assertThat(testObs.values().size()).isEqualTo(2);
testObs.assertValueAt(0, "1");
testObs.assertValueAt(1, "3");
}
The result is that no more items are emitted after "2" gives the error. I've also tried to warp on my own Notification object (MyNotification<T>) and do something like:
stringObs
.map(string -> MyNotification.success(string)
.onErrorReturn(error -> MyNotification.error())
But the end result is always the same: after "2" no more items are emitted. I'm 100% doing something wrong but can't really understand what is.
With flatMap, if one of the inner Observables fails, the sequence is terminated an no further items are transformed from the upstream. That happens before materialize() even gets involved.
So instead of trying to materialize the merged flow, materialize the inner sources individually:
Observable.fromArray("1", "2", "3")
.flatMap(x -> {
if (x.equals("2")) {
return Observable.<String>error(new NullPointerException())
.materialize();
}
return Observable.just(x)
.materialize();
})
.filter(n -> n.isOnNext())
.map(n -> n.getValue());
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'm using retrofit, rxjava and realm to build an application. This is what I'm trying to accomplish:
Load data from local DB and at same time issue network request
First time display a loader only
If data is already there in DB display it and show small loader on somewhere
When network results are ready update the list and also save the results to disk
If network results failed then display a message to use saying that data could be outdated.
I know how to use realm, retrofit properly but its the rxjava part that's confusing. Is there an easy way to do this with rxjava?
This is how the current codebase looks like:
CategoryRepository.java
public Observable<List<Category>> getCategories() {
return getCategoriesFromNetwork()
.observeOn(Schedulers.computation())
.doOnNext(this::saveCategoriesToDisk)
.publish(nwResponse -> Observable.merge(nwResponse, getCategoriesFromDisk().takeUntil(nwResponse)));
}
private Observable<List<Category>> getCategoriesFromNetwork() {
return service.getCategories()
.map(categoryListResponse -> categoryListResponse.getData());
}
private Observable<List<Category>> getCategoriesFromDisk() {
return Observable.just(realm.copyFromRealm(
realm.where(Category.class).findAll()
));
}
ViewModel
categoryRepository.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(categories -> {
//Do UI stuff
}, throwable -> {
});
To call both functions at the same time you can use the Zip operator or the combineLatest operator. They are essentially the same, I think the difference is for zip to continue you need two new observables and for combine latest you just need one new observable for it to continue to the lambda.
Do something list this:
public Observable<List<Category>> getCategories() {
return Observable.combineLatest(getCategoriesFromNetwork(), getCategoriesFromDisk(), (categoriesFromNet, categoriesFromDisk) -> {
categoriesFromNet.addAll(categoriesFromDisk);
return categoriesFromNet;
}) // now you have a complete list of your categories to do with what you want
I have a chain of Rx Completables that I want to run one after another. I am using concat() to do this since I do not want them all to start at the same time.
view.welcome_message_edittext.verifyNotEmpty(getString(R.string.enter_your_email_address))
.concatWith(view.welcome_message_edittext.verifyEmailAddress())
.concatWith(sendMessageToBot())
.subscribe({
// The user has successfully entered data into the edittext, entered an email into the edittext, and sent message to bot.
}, { error -> })
The code above is saying this, "Assert the user has entered text into the EditText. If that is true, assert the user has entered an email into the EditText. If both of those are true, send a message to the bot." If the user enters text into the EditText but it is not an email, I expect the chain of Completables to break and onError() gets called.
This is what I want to happen ^^^. When any of the Completables calls onError() (as verifyNotEmpty() and verifyEmailAddress() do if user leaves EditText empty or does not enter email address) then I expect the entire chain to terminate and call the .subscribe() onError() function.
But, looking at the docs for .concat() this is the actual behavior of it:
concat() will simply move onto the next Completable when onError is called. The chain continues.
So my question is, what do I need to use in order to break the chain when any of the Completables call onError()?
Thanks to #Buckstabue in the comments for helping me debug this issue. His comment:
Let me guess. It's absolutely normal that the method verifyEmailAddress() is called and I suspect you are doing some business logic right there outside of an observable. You can put that logic inside the observable and it will be calculated lazily It's similar to difference between Observable.just(getMyInteger()) and Observable.fromCallable(() -> getMyInteger()). In the second case getMyInteger() will be lazily called after subscribing while the first one is called immediately
Went back to my code and viewed my verifyEmailAddress() and sendMessageToBot() functions:
private fun sendMessageToBot(): Completable {
insertChatMessageIntoConversation(ChatMessage(view!!. welcome_message_edittext.text.toString()))
return Completable.complete()
}
fun EditText.verifyEmailAddress(): Completable {
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(text.trim()).matches()) {
return Completable.error(RuntimeException("Enter a valid email"))
} else {
return Completable.complete()
}
}
The logic of the functions were not inside of a Completable block. I did not think that this mattered when I wrote the code because I thought that Rx's behavior was that it executed each Completable and waited for them to complete or error completely before moving onto the next Completeable. Therefore, skipping the sendMessageToBot() and verifyEmailAddress() functions entirely. Not the case.
This works:
fun EditText.verifyEmailAddress(): Completable {
return Completable.fromCallable({
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(text.trim()).matches()) {
val errorMessage = context.getString(R.string.enter_email_address)
error = errorMessage
throw RuntimeException(errorMessage)
}
})
}
private fun sendMessageToBot(): Completable {
return Completable.fromCallable {
insertChatMessageIntoConversation(sage(view!!. welcome_message_edittext.text.toString()))
}
}
My app has a SearchView. When the user types in the SearchView the onQueryTextChange passes the query to the presenter and then it calls the API. I am using Retrofit and RxJava for the calls. The calls return a json file with the words containing what the user typed so far. The problem is that, if the user is fast to type letters and the network is slow sometimes the SearchView doesn't show the results based on all the typed letters but maybe up to the second last because the last call was quicker to get the results compared to the second last.
Example:
the user start typing:
"cou" -> make a call to the API (first call after 3 letters) -> start returnin values
"n" -> make a call -> start returning values
"t" -> make a call -> start returning values
"r" -> make a call (the connection is slow)
"y" -> make a call -> start returning values
-> "r" get the results finally and the returns them
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
}
The call is very simple and whenever I get an error I don't want to display anything.
Is there a way to solve that? Or maybe this is not the case to use reactive programming?
EDIT:
Just to make more clear, the flow is the following:
Activity that uses a custom search view (https://github.com/Mauker1/MaterialSearchView)
the custom searchview has a listener when the user starts typing. Once the user starts typing the activity calls the Presenter.
the presenter will subscribe an observable returned by the interactor:
presenter:
addSubscription(mInteractor.getValues(query)
.observeOn(mMainScheduler)
.subscribeOn(mIoScheduler)
.subscribe(data -> {
getMvpView().showValues(data);
}, e -> {
Log.e(TAG, e.getMessage());
}));
interactor:
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(2, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
So now either I change the custom search view in a 'normal' searchview and then use RxBinding or maybe I should use an handler or something like that (but still struggling how to fit it in my architecture)
Firstly make your Searchview as Observable so that you can apply Rx operators.
To convert searchview into Observable
public static Observable<String> fromview(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String s) {
subject.onComplete();
searchView.clearFocus(); //if you want to close keyboard
return false;
}
#Override
public boolean onQueryTextChange(String text) {
subject.onNext(text);
return false;
}
});
return subject;
}
private void observeSearchView() {
disposable = RxSearchObservable.fromview(binding.svTweet)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(text -> !text.isEmpty() && text.length() >= 3)
.map(text -> text.toLowerCase().trim())
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
You can apply filter, condition
RxJava debounce() operator to delay taking any action until the user pauses briefly.
Use of distinctUntilChanged() ensures that the user can search for the same thing twice, but not immediately back to back
The filter operator is used to filter the unwanted string like the empty string in this case to avoid the unnecessary network call.
Handling searchview withRXJava
You're in luck there's an operator for that called debounce
Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(3, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
What debounce does is wait N time units for more results prior to continuing. Say for example the network takes 2 seconds to return and you flood it with request after request, debounce will wait for 3 seconds of no results and then return the last result. Think of it as dropping everything but the one before N time of inactivity.
This solve your problem but will still flood the network, ideally you would use the excellent RxBinding library do the defer prior to making the request something like:
RxTextView.textChanges(searchView)
.debounce(3, TimeUnit.SECONDS)
.map(input->mNetworkService.getAPI().getValues(input.queryText().toString()))
.retry(2)
.onErrorReturn(e -> new ArrayList<>()))
With the current setup it will wait 3 seconds after a user types something and only then make the network call. If instead they start typing something new, the first pending search request gets dropped.
Edit: changed to RxTextView.textChanges(textview) based on OP not using an android SearchView widget
Extending on what #MikeN said, if you want to only use the results of the LAST input, you should use switchMap() (which is flatMapLatest() in some other Rx implementations).
I solved the flooding issue without using RxBinding and I want to post my solution just in case someone else needs it.
So whenever the onTextChanged is called I check, first of all, if the size is > 2 and if it is connected to the network (boolean updated by a BroadcastReceiver). Then I create message to be sent has delayed and I delete all the other messages in the queue. This means that I will execute only the queries that are not within the specified delay:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.getTrimmedLength(s) > 2 && isConnected) {
mHandler.removeMessages(QUERY_MESSAGE);
Message message = Message.obtain(mHandler, QUERY_MESSAGE, s.toString().trim());
mHandler.sendMessageDelayed(message, MESSAGE_DELAY_MILLIS);
}
}
Then the Handler:
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.what == QUERY_MESSAGE) {
String query = (String)msg.obj;
mPresenter.getValues(query);
}
}
};
Add rxbinding dependency to gradle implementation "com.jakewharton.rxbinding2:rxbinding-kotlin:2.1.1"
Use debounce and distinct for ignoring frequent key input and duplicate input
Dispose previous API call for getting only latest search result
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.toolbar_menu, menu)
// Associate searchable configuration with the SearchView
val searchManager = requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.setSearchableInfo(
searchManager.getSearchableInfo(requireActivity().componentName)
)
searchView.maxWidth = Integer.MAX_VALUE
// listening to search query text change
disposable = RxSearchView.queryTextChangeEvents(searchView)
.debounce(750, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
callApi(it.queryText().toString())
}, {
Timber.e(it)
})
super.onCreateOptionsMenu(menu, inflater)
}
private fun callApi(query: String){
if(!apiDisposable.isDisposed){
apiDisposable.dispose()
}
apiDisposable = mNetworkService.getAPI(query)
}
I'm playing around with RXJava, retrofit in Android. I'm trying to accomplish the following:
I need to poll periodically a call that give me a Observable> (From here I could did it)
Once I get this list I want to iterate in each Delivery and call another methods that will give me the ETA (so just more info) I want to attach this new info into the delivery and give back the full list with the extra information attached to each item.
I know how to do that without rxjava once I get the list, but I would like to practice.
This is my code so far:
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.map(tick -> RestClient.getInstance().getApiService().getDeliveries())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(deliveries -> {
MainApp.getEventBus().postSticky(deliveries);
});
This is giving me a list of deliveries. Now I would like to accomplish the second part.
Hope I been enough clear.
Thanks
Finally I found a nice way to do it.
private void startPolling() {
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.flatMap(tick -> getDeliveriesObs())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(this::parseDeliveries, Throwable::printStackTrace);
}
private Observable<List<Delivery>> getDeliveriesObs() {
return RestClient.getInstance().getApiService().getDeliveries()
.flatMap(Observable::from)
.flatMap(this::getETAForDelivery)
.toSortedList((d1, d2) -> {
if (d1.getEta() == null) {
return -1;
}
if (d2.getEta() == null) {
return 1;
}
return d1.getEta().getDuration().getValue() > d2.getEta().getDuration().getValue() ? 1 : -1;
});
}
Let's go step by step.
First we create an Observable that triggers every POLLING_INTERVAL time the method getDeliveriesObs() that will return the final list
We use retrofit to get an Observable of the call
We use flatMap to flattern the resut list and get in the next flatmap a Delivery item, one by one.
Then we get the estimated time of arrival set inside the Delivery object and return it
We sort the list to order by estimated time of arrival.
In case of error we print and retry so the interval does not stop
We subscribe finally to get the list sorted and with ETA inside, then we just return it or whatever you need to do with it.
It's working properly and it's quite nice, I'm starting to like rxjava :)
I haven't spent a lot of time with Java 8 lambdas, but here's an example of mapping each object to a different object, then getting a List<...> out at the other end in plain ol' Java 7:
List<Delivery> deliveries = ...;
Observable.from(deliveries).flatMap(new Func1<Delivery, Observable<ETA>>() {
#Override
public Observable<ETA> call(Delivery delivery) {
// Convert delivery to ETA...
return someEta;
}
})
.toList().subscribe(new Action1<List<ETA>>() {
#Override
public void call(List<ETA> etas) {
}
});
Of course, it'd be nice to take the Retrofit response (presumably an Observable<List<Delivery>>?) and just observe each of those. For that we ideally use something like flatten(), which doesn't appear to be coming to RxJava anytime soon.
To do that, you can instead do something like this (much nicer with lambdas). You'd replace Observable.from(deliveries) in the above example with the following:
apiService.getDeliveries().flatMap(new Func1<List<Delivery>, Observable<Delivery>>() {
#Override
public Observable<Delivery> call(List<Delivery> deliveries) {
return Observable.from(deliveries);
}
}).flatMap(...)