Chaining observables based on result - android

I am a complete beginner on rx-java and rx-android. I've heard the learning curve is quite steep in the beginning.
Im trying to replace all Eventbus based code to a more typesafe alternative by using rx-android.
I've set up this snippet to create observables from edit text text change events:
MainActivity
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe(new Action1<EditText>() {
#Override
public void call(EditText editText) {
searchStopResultFragment.query(editText.getText().toString());
}
});
RxUtils:
public static Observable<EditText> createEditTextChangeObservable(final EditText editText){
return Observable.create(new Observable.OnSubscribe<EditText>() {
#Override
public void call(final Subscriber<? super EditText> subscriber) {
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (subscriber.isUnsubscribed()) return;
subscriber.onNext(editText);
}
});
}
});
}
SearchStopResultFragment:
public void query(String query){
lastQuery = query;
resultObservable = StopProvider.getStopResultObservable(getActivity().getContentResolver(),query);
subscription = resultObservable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Stop>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Stop> stops) {
if(!lastQuery.equals("")) {
if(stops.size()>0) {
ArrayList<AdapterItem> items = adapter.getItems();
items.clear();
for (Stop stop : stops) {
SearchResultStopItem item = new SearchResultStopItem(stop, SearchResultStopItem.STOP);
items.add(item);
}
adapter.setItems(items);
adapter.notifyDataSetChanged();
}else{
//DO A NOTHER ASYNC QUERY TO FETCH RESULTS
}
}else{
showStartItems();
}
}
});
}
It feels like i'm doing this wrong. I create new observables from the query method in my fragment on every text change event. I also want to create a new async lookup operation based off the result in StopProvider.getStopResultObservable (see the comment)
Any thoughs?

Here is what I came up with:
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.map(EXTRACT_STRING)
.filter(STRING_IS_NOT_EMPTY)
.concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(final String query) {
return StopProvider.getStopResultObservable(getContentResolver(), query)
.map(new Func1<List<Stop>, Pair<String, List<Stop>>>() {
// I think this map is a bit more readable than the
// combineLatest, and since "query" should not be changing
// anyway, the result should be the same (you have to
// declare it as final in the method signature, though
#Override
public Pair<String, List<Stop>> call(List<Stop> stops) {
return new Pair(query, stops);
}
});
}
)
.concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first)
.map(new Func1<LocationNameResponse, List<Stop>>() {
#Override
public List<Stop> call(LocationNameResponse locationNameResponse) {
// since there was no if-else in your original code (you were always
// just wrapping the List in an Observable) I removed that, too
return locationNameResponse.getAddresses();
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
// since I don't know what your API is returning I think
// it's saver to keep this check in:
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
where:
public static final Func1<EditText, String> EXTRACT_STRING = new Func1<EditText, String>() {
#Override
public void String call(EditText editText) {
return editText.getText().toString();
}
};
public static final Func1<String, Boolean> STRING_IS_NOT_EMPTY = new Func1<String, Boolean>() {
#Override
public void String call(String string) {
return !string.isEmpty();
}
};
So, this at least removes the need to return Observable.just(null) and then check for that down the chain.

You can move your second concatMap to the only place you need it - after combineLatest
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.concatMap(new Func1<EditText, Observable<Pair<String, List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if (query.isEmpty()) {
return Observable.just(null);
}
return Observable
.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s, stops);
}
})
.concatMap(new Func1<R, Observable<? extends Pair<String, List<Stop>>>>() {
#Override
public Observable<? extends Pair<String, List<Stop>>> call(R r) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
});
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});

Solved it using concatmap and combine latest:
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if(query.isEmpty()){
return Observable.just(null);
}
return Observable.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s,stops);
}
});
}
}).concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if(queryAndStops!=null) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
return Observable.just(null);
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle()).subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
}else{
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
However is there some nicer way to break out of the chain without sending Observable.just(null) and check for nulls in next call?

Related

Chaining multiple stream with rxjava2

I try to write code call service API by RxJava2.
I have multiple API(included loop call API).
Here is my example code that i want.
My question is why onComplete haven't call?
Observable.just("test")
.flatMapCompletable(new Function<String, CompletableSource>() {
#Override
public CompletableSource apply(String s) throws Exception {
ArrayList<Observable<Void>> arrayList = new ArrayList<>();
arrayList.add(Completable.complete().delay(3, TimeUnit.SECONDS).andThen(new Observable<Void>() {
#Override
protected void subscribeActual(Observer<? super Void> observer) {
Log.e("subscribeActual", "onComplete1");
observer.onComplete();
}
}));
arrayList.add(Completable.complete().delay(1, TimeUnit.SECONDS).andThen(new Observable<Void>() {
#Override
protected void subscribeActual(Observer<? super Void> observer) {
Log.e("subscribeActual", "onComplete2");
observer.onComplete();
}
}));
arrayList.add(Completable.complete().delay(2, TimeUnit.SECONDS).andThen(new Observable<Void>() {
#Override
protected void subscribeActual(Observer<? super Void> observer) {
Log.e("subscribeActual", "onComplete3");
observer.onComplete();
}
}));
return Observable.merge(arrayList)
.toList()
.flatMapCompletable(new Function<List<Void>, CompletableSource>() {
#Override
public CompletableSource apply(List<Void> voids) throws Exception {
return Completable.complete();
}
});
}
})
.andThen(new Observable<Void>() {
#Override
protected void subscribeActual(Observer<? super Void> observer) {
Log.e("subscribeActual", "subscribeActual");
observer.onNext(null);
}
})
.flatMap(new Function<Void, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(Void aVoid) throws Exception {
Log.e("ObservableSource", "apply");
return Observable.just("Hello");
}
})
.flatMapCompletable(new Function<String, CompletableSource>() {
#Override
public CompletableSource apply(String s) throws Exception {
Log.e("apply", s);
return Completable.complete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Log.e("onComplete", "onComplete");
}
#Override
public void onError(Throwable e) {
Log.e("onError", "onError " + e.getMessage());
}
});
And result from Log is
subscribeActual: onComplete2
subscribeActual: onComplete3
subscribeActual: onComplete1
subscribeActual: subscribeActual
ObservableSource: apply
apply: Hello
I expect chaining observable end with "Void" before subscribe(Completed or Observable<Void>).

Changing AsyncTask to Rxjava

I have a challenge with converting the AsyncTask doInBackground process to RxJava. I would love to know how to convert this to Rx Java as none of what I've tried is working.
new AsyncTask<Void, Void, Integer>() {
#Override
protected Integer doInBackground(Void... voids) {
return mDAO.getCount();
}
#Override
protected void onPostExecute(Integer count) {
if (count == 0)
mCount.setText("All Notifications");
else
mCount.setText("New Notificaiton "+count);
}
}.execute();
And I tried this for Rx
Observable<Integer> count = Observable.fromCallable(new Callable<Integer>() {
#Override
public Integer call() throws Exception {
return mDAO.getCount();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
count.subscribe(new Observer<Integer>() {
#Override
public void onSubscribe(Disposable d) {
mDisposable.add(d);
}
#Override
public void onNext(Integer integer) {
if (integer == 0)
mCount.setText("All Notifications");
else
mCount.setText("New Notification "+count);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I get this for instead of the count
Count io.reactivex.internal.operators.observable.ObservableObserveOn#5ccee5b
How do I solve this? Thank you.
Your implementation is why you're having this error. You should use a single callable instead. This should work 100% and let me know if you have any challenge with it.
Single.fromCallable(new Callable<Integer>() {
#Override
public Integer call() throws Exception {
return mDAO.getCount();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(new Consumer<Integer>() {
#Override
public void accept(Integer integer) throws Exception {
if (integer == 0)
mCount.setText("All Notifications");
else
mCount.setText("New Notification "+integer);
}
})
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
}
})
.subscribe();

Make n asynchronous calls from list and invoke method when all calls are completed

I make n asynchronous calls (n being the size of the arraylist, and indexes passed as integer parameters to the calls) and want to invoke a method when all of the calls are completed. I implemented the following code below. I used a counter to know that all of the calls are completed. It is working, however I know that it could be done in a more efficient and elegant way.
int n = mUserUrls.getM3u().size();
counter = n;
Observable.range(0, n)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<Integer>() {
#Override
public void accept(Integer integer) throws Exception {
final int index = integer;
Single<ResponseBody> singleGetChannels = aPI.getChannels(mUserUrls.getM3u().get(integer))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Single<List<EPG>> singleGetEPGs = aPI.getEPGs(mUserUrls.getJson())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Single.zip(singleGetChannels, singleGetEPGs, new BiFunction<ResponseBody, List<EPG>, ChannelsAndEPG>() {
#Override
public ChannelsAndEPG apply(ResponseBody responseBodyChannels, List<EPG> ePGs) {
ChannelsAndEPG channelsAndEPG = new ChannelsAndEPG(responseBodyChannels, ePGs);
return channelsAndEPG;
}
}).subscribe(new SingleObserver<ChannelsAndEPG>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(ChannelsAndEPG channelsAndEPG) {
m3Us.put(index, M3UParser.parseList(channelsAndEPG.mResponseBodyChannels.byteStream()));
setEPGs(index, channelsAndEPG.mEPG);
setEPGsForNext24Hours();
counter--;
if (counter == 0) {
if (mCallback != null) {
isDataLoaded = true;
mCallback.onDataLoaded();
}
}
}
#Override
public void onError(Throwable e) {
}
});
}
})
.subscribe(new Observer<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Integer integer) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Log.i(TAG, "onComplete called");
}
});
You can use flatMap to convert each integer to Single ( same way you're doing it now). And then call toList to get Single.
You could use this :
Observable.fromIterable(mUserUrls.getM3u())
.flatMap{ m3u ->
aPI.getChannels(m3u.getInteger)
.zipWith(aPI.getEPGs(mUserUrls.getJson()))
.subscribeOn(Schedulers.io())
}
.doOnNext{
m3Us.put(index, M3UParser.parseList(channelsAndEPG.mResponseBodyChannels.byteStream()));
setEPGs(index, channelsAndEPG.mEPG);
setEPGsForNext24Hours();
}
.subscribe(new Observer<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Integer integer) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Log.i(TAG, "onComplete called");
}
})

Paging Library for those which don't have any numeric Id

I am actually consuming Triposo's API to show the list of countries. https://www.triposo.com/api/
However, in responses, there is no numeric Id and I am struggling while incrementing page size. What should I perform on such responses to overcome? For instance, Github API is fully suitable for Paging Library since it returns itemId as numeric.
https://api.github.com/users
Example Response
{
"results": [
{
"name": "Benin",
"country_id": "Benin",
"snippet": "A safe and relatively easy country for travellers to visit; birthplace of the Voodoo religion and former home of the Kingdom of Dahomey.",
"parent_id": null,
"score": 3.98127216481287,
"id": "Benin"
}
],
"estimated_total": 30845,
"more": true
}
ItemKeyedDataSource.java
public class ItemKeyedCountryDataSource extends ItemKeyedDataSource<Integer, CountryResult> {
private EndpointHelper endpointHelper;
ItemKeyedCountryDataSource() {
endpointHelper = EndpointHelper.getInstance();
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull final LoadInitialCallback<CountryResult> callback) {
final List<CountryResult> countryResultList = new ArrayList<>();
endpointHelper.getCountryList(0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() {
#Override
public Observable<? extends CountryWrapper> call(Throwable throwable) {
return Observable.error(throwable);
}
})
.subscribe(new Subscriber<CountryWrapper>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(CountryWrapper countryWrapper) {
if (countryWrapper.getResults().size() > 0) {
countryResultList.addAll(countryWrapper.getResults());
callback.onResult(countryResultList);
}
}
});
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull final LoadCallback<CountryResult> callback) {
final List<CountryResult> countryResultList = new ArrayList<>();
endpointHelper.getCountryList(params.requestedLoadSize)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() {
#Override
public Observable<? extends CountryWrapper> call(Throwable throwable) {
return Observable.error(throwable);
}
})
.subscribe(new Subscriber<CountryWrapper>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(CountryWrapper countryWrapper) {
if (countryWrapper.getResults().size() > 0) {
countryResultList.addAll(countryWrapper.getResults());
callback.onResult(countryResultList);
}
}
});
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<CountryResult> callback) {
//Do nothing
}
#NonNull
#Override
public Integer getKey(#NonNull CountryResult item) {
return ?; // what do I return here?
}
}
ViewModel.java
public class SearchableActivityViewModel extends ViewModel {
private LiveData<PagedList<CountryResult>> countryResult;
SearchableActivityViewModel() {
Executor executor = Executors.newFixedThreadPool(5);
CountryResultDataSourceFactory countryResultDataSourceFactory = new CountryResultDataSourceFactory();
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(20) //first load
.setPageSize(21)
.build();
//noinspection unchecked
countryResult = new LivePagedListBuilder<String, CountryResult>(countryResultDataSourceFactory, config)
.setFetchExecutor(executor)
.build();
}
public LiveData<PagedList<CountryResult>> getCountryResult(){
return countryResult;
}
#Override
protected void onCleared() {
super.onCleared();
}
}
Thanks in advance. Hope I am clear.
Best,

Rxjava : Apply treatment on members of an object

I hava a function which is returning an object containing a list from a html page :
#Override
public Observable<MediaList> getMediaListFromUrl(String url) {
return getHtml(url)
.map(new Func1<String, MediaList>() {
#Override
public MediaList call(String s) {
return interpreter.interpretMediaResultsFromHtml(s);
}
});
}
What I would like to do next is:
for(Media media : mediaList.getMedias()) {
if (media.getImageUrl().contains("couvertureAjax")) {
// fetch media.getImageUrl
// replace the image url with the result
}
}
and at the end, return the original MediaList object with the replaced image urls.
I think I should use flatmap and from somewhere, but I don't know exactly how.
MediaList.java:
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
public class MediaList implements Parcelable {
public static final String TAG = MediaList.class.getSimpleName();
private List<Media> medias;
private String nextPageUrl;
public MediaList() {}
protected MediaList(Parcel in) {
medias = in.createTypedArrayList(Media.CREATOR);
nextPageUrl = in.readString();
}
public List<Media> getMedias() {
return medias;
}
public void setMedias(List<Media> medias) {
this.medias = medias;
}
public String getNextPageUrl() {
return nextPageUrl;
}
public void setNextPageUrl(String nextPageUrl) {
this.nextPageUrl = nextPageUrl;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(medias);
dest.writeString(nextPageUrl);
}
#Override
public int describeContents() {
return 0;
}
public static final Creator<MediaList> CREATOR = new Creator<MediaList>() {
#Override
public MediaList createFromParcel(Parcel in) {
return new MediaList(in);
}
#Override
public MediaList[] newArray(int size) {
return new MediaList[size];
}
};
}
As I am not having all classes that are involved here, so I have written a Rx code that would help to achieve what you want:
getHtml(url)
.flatMap(new Func1<String, Media>() {
#Override
public Media call(String s) {
// Here update code interpreter.interpretMediaResultsFromHtml(s) and make sure your return List<Media> from here
return Observable.from(interpreter.interpretMediaResultsFromHtml(s));
}
})
.map(new Func1<Media, Media>() {
#Override
public Media call(Media media) {
// Write code here to update Url in media and return it
return null; // Return updated media object from here
}
})
.toList()
.subscribe(new Action1<List<Media>>() {
#Override
public void call(List<Media> mediaList) {
// Here MediaLkist object will be your updated List<Media>
}
});
Let me know if it fix your issue or you need more details. It's definitely possible what you want to achieve via RxJava.
One way to do this :
#Override
public Observable<MediaList> getMediaList(String url) {
return getHtml(url)
.flatMap(new Func1<String, Observable<MediaList>>() {
#Override
public Observable<MediaList> call(String s) {
MediaList mediaList = interpreter.interpretMediaResultsFromHtml(s);
Observable<List<Media>> list = Observable.from(mediaList.getMedias())
.flatMap(new Func1<Media, Observable<Media>>() {
#Override
public Observable<Media> call(Media media) {
if (media.needImagePreload()) {
return getMediaLoadedImage(media.getImageUrl(), media);
} else {
return Observable.just(media);
}
}
})
.toList();
return Observable.zip(Observable.just(mediaList.getNextPageUrl()), list, new Func2<String, List<Media>, MediaList>() {
#Override
public MediaList call(String s, List<Media> medias) {
return zipMediaList(s, medias);
}
});
}
});
}
private MediaList zipMediaList(String s, List<Media> medias) {
MediaList mediaList = DefaultFactory.MediaList.constructDefaultInstance();
mediaList.setNextPageUrl(s);
mediaList.setMedias(medias);
return mediaList;
}
I think they are easier ways to do this, but it works.

Categories

Resources