RxJava2 map/flatMap with flatMapIterable - android

Here's what I am trying to achieve:
I have an API response say myResponse, which contains List of item. I want to iterate through each item through flatMapIterable which works fine. But before doing that I would like to check if API didn't return error or List size is > 0. I am not able to achieve this.
Here's the code what I have tried so far:
// This works & I am able to iterate through each item
myApi.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMapIterable(response -> response.getData().getItems())
.subscribeWith(new DisposableObserver<Mobile>() {
#Override
public void onNext(#NonNull Item item) {
Log.e("TAG", "item is => " + item.getItemName());
}
#Override
public void onError(#NonNull Throwable e) {
}
#Override
public void onComplete() {
Log.e("TAG", "processing completed");
}
});
But I am not sure how should I use map or flatmap before flatMapIterableto check for any error. I am doing something like following without any success:
myApi.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(response -> {
if(response.getStatus() == 200) {
List<Item> items = response.getData().getItems();
if(items == null || items.size() < 1)
return Observable.error(new Throwable("No data!"));
return Observable.just(response);
}
return Observable.error(new Throwable(response.getData().getMsg()));
})
.doOnError(Throwable::getMessage)
.flatMapIterable(response -> response.getData().getItems());
Here on flatMapIterable, I get error cannot resolve getData(). because I believe It's not able to identify response as MyResponse.class. I am stuck here, please let me know how can I achieve what I want with RxJava2?

In map(response -> {...}) you have created Observable of MyResponse, that is why it does not compile. Just replace map(response -> {...}) with flatMap(response -> {...}) or add flatMap(r -> r) before flatMapIterable.

Related

Filter Observable<List> with text from an EditText using RxBinding

I have an array of Objects and I want to filter that array based on the text user types on an EditText android view.
What I thought it that I should try and convert the array of POJOs to an Observable of Strings and this is what I did :
Observable<String> professionsObservable = Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
if (profession.getName().length() > 0) {
professionsNameList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsNameList;
})
.flatMapIterable(items -> items);
Now I want to combine the text from the EditText with the `professionsObservable I posted above.
This is the code I'm using :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.switchMap(search -> {
return professionsObservable
.filter(profession -> {
return profession.toLowerCase().startsWith(search);
});
}
)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
Timber.i("NOT ENTERING");
rvProfession.setVisibility(VISIBLE);
professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
I'm using map operator to turn the text change event to a String and then for each String I get from the stream I'm using switchMap (cause I don't care for results from previous searches). Then I compose all Strings to a List with toList. The problem is that it never reaches the subscribe call while I have a lot of strings in the initial Array I used and I do type text that fits the condition of the filter operator.
Is it something that I might missing here ?
EDIT : I updated my code to :
RxTextView.textChangeEvents(etProfession)
.doOnEach(notif -> {
if (etProfession.getText().toString().trim().length() > 0) {
etCompany.setVisibility(GONE);
etIndustry.setVisibility(GONE);
} else {
etCompany.setVisibility(VISIBLE);
etIndustry.setVisibility(VISIBLE);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.debounce(EDITTEXT_DELAY, TimeUnit.MILLISECONDS)
.skip(1)
.map(textChangeEvent -> textChangeEvent.text().toString())
.flatMap(search -> {
return Observable.fromArray(((GetStartedActivity) getActivity()).professions)
.map(profession -> {
List<String> professionsList = new ArrayList<>();
if (profession.getName().length() > 0) {
professionsList.add(capitalizeFirstLetter(profession.getName()));
}
return professionsList;
})
.flatMapIterable(items -> items)
.filter(profession -> {
if (profession.toLowerCase().startsWith(search.toLowerCase())) {
}
return profession.toLowerCase().startsWith(search.toLowerCase());
});
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
filteredProfessions -> {
rvProfession.setVisibility(VISIBLE);
// professionAdapter.addItems(filteredProfessions);
},
throwable -> Log.i("THROW", "PROFESSIONS ", throwable));
If I remove toList() operator my code works (enters the subscribe call) but if I leave it there it wont. Anyone knows why ?
In my experience, RxBinding requires .subscribeOn(AndroidSchedulers.mainThread()) right after .textChangeEvents() or any other event. So probably you are causing it to fail by adding .subscribeOn(Schedulers.io)
See method .checkMainThread() in https://github.com/JakeWharton/RxBinding/blob/master/rxbinding/src/main/java/com/jakewharton/rxbinding2/internal/Preconditions.java
UPDATE
Also, probably because .onComplete() never comes from upstream, .toList() is never executed.

RxJava .filter is blocking other code from sending data to API

I'm using RxJava inside an evernote job to send some data to API. This code was working just fine till now. The problem is somewhere in the .filter I think as it isn't even getting to getApiService().createReport(requestModel) method unless there are photos in the model (then the report.getImages() is not null.
public static Observable<Report> createReport(CreateReportModel model) {
return Observable.just(model)
.filter(report -> report.getImages() != null)
.flatMap(report -> {
return Observable.from(report.getImages())
.map(photoModel -> {
return photoModel;
})
.filter(photoModel -> photoModel.hasImage())
.filter(photoModel -> photoModel.getImage().exists())
.flatMap(photoModel -> uploadFile(photoModel)).toList();
})
.map(photoModels -> model)
.flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
}
This function is called inside this code
Observable<PalletReport> report = createReport(model);
report.subscribe(new Subscriber<PalletReport>() {
#Override
public void onCompleted() {
resultHolder.setResult(Result.SUCCESS);
#Override
public void onError(Throwable e) {
Timber.d(e, "Upload Error");
resultHolder.setResult(Result.RESCHEDULE);
}
#Override
public void onNext(PalletReport model) {
Timber.d("On Next " + model);
}
});
And here it goes to Result.SUCCESS but the response isn't get and the report isn't create on back end. My concern is that this code was working just fine a few days ago, and without any changes it stopped.
[EDIT]
I have this function that is called inside the first flatMap, and it's used to send the photos.
#NonNull
private static Observable<? extends CreatePalletPhotoModel> uploadPalletFile(CreatePalletPhotoModel photo) {
MultipartBody.Part fileBody = Paperless.createFileBody(photo.getImage());
return Paperless.getApiService().uploadPalletPhoto(fileBody)
.map(upload -> {
photo.setPalletStatus(upload.getPalletStatus());
photo.setImage(upload.getImage());
return photo;
});
}
If there are no reports after filter nothing will get executed. Consider removing
.map(photoModels -> model)
and just end the first observable there (you would need to subscribe to it) and start again with
Observable.just(model).flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
that will ensure that getApiService call is always executed.

Api call and show data with RxJava and Retrofit

I'm using Retrofit to get list of pallet types from Api.
This is how my call looks like.
#GET("pallettypes")
Observable<List<PalletType>> getPalletTypes();
Then I have some function that gets the response from the api and map it (I don't know if I'm using the map function as it should - new to RxJava)
private static Observable<List<PalletType>> getTypes() {
return getApiService().getPalletTypes()
.map(response -> {
//Here i need some code to get the response from the api and put it in Store.palletTypes()
}
return response;
And then to call the function in the onViewCreated part.
getTypes().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() ->{
})
.doOnTerminate(() -> {
})
.subscribe(response -> {
PalletsManager.getInstance().setTypes(response);
populateTypes(response);
}, throwable -> {
});
I need populateTypes function to show the type using a custom view
public void populateTypes (List<PalletType> palletTypes) {
for(PalletType type : palletTypes) {
palletView = new PalletView(getContext());
palletView.setLabel(type.getType());
delivered_pallets.addView(palletView);
}
}
This is my idea but it doesn't work because I never get in the .subscribe block and noting is shown.

RxJava: Continue next iteration even if error occurs

I'm using RxSearchView to emit out the results of a search query from an API to a recyclerview. However, if one of those query fails, onError() is called(which is expected) but the subscription as a whole is also canceled. Subsequent queries are not executed at all.
How should i modify the code so that the call to onError() is prevented when a query fails and the next incoming queries are executed normally?
Here's a code snippet:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService.getSearchResults(query))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
P.S: I am using switchMap() so that the results of old queries are ignored, if the results of new query has arrived.
You have to handle this error and return an object instead. You can do it, for example, by using onErrorResumeNext operator with apiService.getSearchResults(query) call. What you are going to return - depends on you, you can even return null if you want, but better to create some wrapper which can carry both response status flag and normal response if received.
Something like:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService
.getSearchResults(query)
.onErrorResumeNext(error -> null)
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse != null && searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
Of course, this is naive example with using null, in reality you need to write error handling logic. Better to return wrapper, because if using RxJava 2, then it doesn't support null.

Update the UI ( Loading and Error Views ) on RecyclerView using RxJava

I have a SearchView that executes a network request to search for some tracks and then populates a RecylerView with the results. I have found this code which works fine.
I have already integrated the RecyclerView EmptyView through the Adapter but now I am trying to integrate the LoadingView(Progress) and ErrorView inside this code. I tried to put the LoadingView(ProgressBar) on Visibility True inside the concatMap but got the error that the “Only the original thread that created a view hierarchy can touch its views.” which can be solved running that on the MainThread but I am sure there is a better way to do this.
Can someone have a better idea where and how the logic about show/hide the ErrorView and LoadingView can be integrated into this code?
I am using also RxBinding. Maybe also using RxRecyclerView would be a good idea?
RxSearchView.queryTextChanges(searchView).
filter(charSequence ->
!TextUtils.isEmpty(charSequence))
.throttleLast(100, TimeUnit.DAYS.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.concatMap(searchTerm ->
{
return searchTracks(searchTerm).
.subscribeOn(Schedulers.io())
.onErrorResumeNext(throwable1 -> {
//handle error somehow, change UI
return Observable.empty();
}
);
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tracks -> {
populateTracks(tracks);
}
});
}, throwable -> {
//show errorView
});
This here was my complete solution without stripping code for the presentation.
RxSearchView.queryTextChanges(searchView)
.skip(1)
.doOnNext(charSequence -> Log.v(TAG, "searching: " + charSequence))
.throttleLast(100, TimeUnit.MILLISECONDS)
.debounce(200, TimeUnit.MILLISECONDS)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.filter(charSequence -> {
final boolean empty = TextUtils.isEmpty(charSequence);
if (empty) {
Log.v(TAG, "empty view");
mAdapter.clear();
}
return !empty;
})
.concatMap(charSequence -> {
Log.v(TAG, "requesting " + charSequence);
return onErrorResumeNext(
mGitApiService.searchRepositoriesSingle(charSequence.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
throwable -> {
try {
throw throwable;
} catch (HttpException httpException) {
showEmptyErrorView(httpException.message());
} catch (Throwable other) {
showEmptyErrorView(other.getMessage());
other.printStackTrace();
}
return Observable.empty();
});
})
.doOnNext(charSequence -> Log.v(TAG, "got data"))
.subscribe(response -> {
showRepositories(response.getItems());
}, throwable -> {
throwable.printStackTrace();
showEmptyErrorView(throwable.getMessage());
});
so basically whenever you touch your view you have to call .observeOn(AndroidSchedulers.mainThread())

Categories

Resources