I will try to be more clear I can.
I want to loop the elements inside a list, for each element in the MAIN_LIST, start an elaboration. The elaboration consists in another list, SECOND_LIST, that has to be looped. When all the elaboration on each item of the SECOND_LIST is finished, start do the same with the next element in the MAIN_LIST.
When finish elaborating all elements in the MAIN_LIST return complete.
Here is I try to achieve this, but I think there is a better way.
Thanks for your help!
Method that loop the MAIN_LIST
public Completable checkGroupExpiration(List<CheckVersion.ServiceStatus> serviceStatusList) {
return Completable.create(emitter -> {
Observable.fromIterable(serviceStatusList)
.concatMapCompletable(serviceStatus -> {
return checkGroupExpiration(serviceStatus.service, serviceStatus.lastUpdate);
}).subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
if (!emitter.isDisposed())
emitter.onComplete();
}
#Override
public void onError(Throwable e) {
if (!emitter.isDisposed())
emitter.onComplete();
}
});
});
}
Method that loop SECOND_LIST
protected Completable checkGroupExpiration(String group, long ttl) {
return Completable.create(emitter -> {
readFile(MASTER_NOTE)
.map(s -> {
return new Gson().fromJson(s, MasterNote.class);
}).flatMapObservable(masterNote -> {
return Observable.fromIterable(masterNote.savedFiles.entrySet());
}).filter(stringCacheInfoEntry -> {
return stringCacheInfoEntry.getValue().group.equals(group) && stringCacheInfoEntry.getValue().ttl < ttl;
}).concatMapCompletable(stringCacheInfoEntry -> {
return getFile(stringCacheInfoEntry.getKey(), false)
.doOnSuccess(file -> {
String fileName = file.getName();
file.delete();
Log.d(TAG, "File deleted => " + fileName + " from group => " + group);
}).ignoreElement();
}).subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
if (!emitter.isDisposed())
emitter.onComplete();
}
#Override
public void onError(Throwable e) {
if (!emitter.isDisposed())
emitter.onComplete();
}
});
});
}
Yes, there is. Don't subscribe to an inner flow inside create but use the flows directly:
public Completable checkGroupExpiration(
List<CheckVersion.ServiceStatus> serviceStatusList) {
retrn Observable.fromIterable(serviceStatusList)
.concatMapCompletable(serviceStatus ->
checkGroupExpiration(serviceStatus.service, serviceStatus.lastUpdate)
)
.ignoreElements();
}
protected Completable checkGroupExpiration(String group, long ttl) {
return
readFile(MASTER_NOTE)
.map(s ->
new Gson().fromJson(s, MasterNote.class)
)
.flatMapObservable(masterNote ->
Observable.fromIterable(masterNote.savedFiles.entrySet())
)
.filter(stringCacheInfoEntry ->
stringCacheInfoEntry.getValue().group.equals(group)
&& stringCacheInfoEntry.getValue().ttl < ttl
)
.concatMapCompletable(stringCacheInfoEntry ->
getFile(stringCacheInfoEntry.getKey(), false)
.doOnSuccess(file -> {
String fileName = file.getName();
file.delete();
Log.d(TAG, "File deleted => " + fileName + " from group => " + group);
})
.ignoreElement()
);
}
Related
i have 4 observable, and i want call it sequence
Observable1 //return boolean
-> Observable2 // if(Observable1 result == true) { call Observable3 } else { call observable4 }
-> Observable3 // call Observable4
-> Observable4
-> subscribe();
i tried it, but when Observable1 result is false onCompleted called
Observable
.fromCallable(new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
return false;
}
})
.flatMap(new Func1<Boolean, Observable<String>>() {
#Override
public Observable<String> call(Boolean aBoolean) {
if(aBoolean) {
return Observable.just("to Observable2");
}
return null; // to Observable 3
}
})
.flatMap(new Func1<String, Observable<Integer>>() {
#Override
public Observable<Integer> call(String s) {
return Observable.just(1); //to Observable3
}
})
.flatMap(new Func1<Integer, Observable<Boolean>>() {
#Override
public Observable<Boolean> call(Integer integer) {
return Observable.just(true); //success
}
})
.subscribe(new Subscriber<Boolean>() {
#Override
public void onCompleted() {
Log.e("TAG", "onCompleted");
}
#Override
public void onError(Throwable e) {
Log.e("TAG", "onError", e);
}
#Override
public void onNext(Boolean bool) {
Log.e("TAG", "onNext");
}
});
how can achieve to this ?
thanks
There are many ways of doing this. Only proposed this based on the first pseudocode you are offering.
Observable.just(boolean)
.flatmap( boolean -> if (boolean) return Observable3
else return Observable.just(boolean))
.flatmap(boolean -> Observable4)
.subscribe()
i solved this problem with define observables as functions
public void run() {
Observable
.fromCallable(() -> false)
.flatMap(bool -> {
if(bool) {
return getObservable2();
}
return getObservable3();
})
.subscribe(bool -> {
Log.e("TAG", "onNext");
}, e -> {
Log.e("TAG", "onError", e);
}, () -> {
Log.e("TAG", "onCompleted");
});
}
private Observable<Boolean> getObservable2() {
return Observable
.just("test")
.flatMap(s -> getObservable4());
}
private Observable<Boolean> getObservable3() {
return Observable
.just(1)
.flatMap(s -> getObservable4());
}
private Observable<Boolean> getObservable4() {
return Observable.just(true);
}
Observable.just(boolean)
.flatmap( boolean -> if (boolean)
return Observable3.flatmap(observable4)
else return Observable4))
.subscribe()
if the result of first observable is true than obervablr3 is called whose result is then flat mapped into the observable4 second case is if the result of first observable is false then it will call observable4 directly and the result is subscribes in first observable only
This is how I would attempt to solve your problem but it doesn't use flatmap.
Observable2.subscribe( aBoolean -> if(aBoolean){
Observable3.subscribe();
} else {
Observable2.subscribe( result -> Observable3.subscribe());
}
You should never return null from an Observable
I have the following methods
Document createDocument(String url);
List<MediaContent> getVideo(Document doc);
List<MediaContent> getImages(Document doc);
List< MediaContent> will be consumed by
void appendToRv(List<MediaContent> media);
I like to use RxJava2 such that
CreateDocument -> getVideo ->
-> appendToRv
-> getImages ->
(also, the video output should be ordered before images).
How would I go about doing that? I tried flatMap, but it seems to only allow a single method to be used
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
// . ?? ..
// this is the part i am lost with
// how do i feed document to -> getVideo() and getImage()
// and then merge them back into the subscriber
//
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription);
The DisposableSingleObserver
parseImageSubscription = new DisposableSingleObserver<List<MediaContent>>() {
#Override
public void onSuccess(List<MediaContent> media) {
if(media!=null) {
appendToRv(media);
}
}
#Override
public void onError(Throwable error) {
doSnackBar("error loading: '" + q + "'");
}
};
the single observables for getVideos and getImages
Single<List<MediaContent>> SingleGetImage(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getImage(document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No images found"));
}
});
}
Single<List<MediaContent>> singleGetVideo(Document document ) {
return Single.create(e -> {
List<MediaContent> result = getVideo( document);
if (result != null) {
e.onSuccess(result);
}else {
e.onError(new Exception("No videos found"));
}
});
}
assuming you want to execute in parallel the getVideos and getImages requests, you can use flatMap() with zip(), zip will collect the 2 emissions from both Singles, and you can combine the 2 results to a new value, meaning you can sort the videos MediaContent list , and combine it with the images MediaContent list, and return unified list (or whatever other object you'd like):
Single<List<MediaContent>> single =
Single.fromCallable(() -> createDocument(url))
.flatMap(document -> Single.zip(singleGetVideo(document), SingleGetImage(document),
(videoMediaContents, imageMediaContents) -> //here you'll have the 2 results
//you can sort combine etc. and return unified object
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
single.subscribe(parseImageSubscription)
Observable.zip() could implement it perfect. The Observer will receive a merged result by this method.
public void zip() {
Observable<Integer> observable1 = Observable.just(1);
Observable<Integer> observable2 = Observable.just(2);
Observable.zip(observable1, observable2, new Func2<Integer, Integer, Integer>() {
#Override
public Integer call(Integer integer, Integer integer2) {
return integer + integer2;
}
}).subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer o) {
Logger.i(o.toString());
//Here will print 3.
}
});
}
mSubscription = RxSearchView.queryTextChangeEvents(mSearchView)
.subscribeOn(AndroidSchedulers.mainThread())
.debounce(600, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter(new Func1<SearchViewQueryTextEvent, Boolean>() {
#Override
public Boolean call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String query = searchViewQueryTextEvent.queryText().toString();
if (!TextUtils.isEmpty(query) && query.length() >= 11) {
if (!CommonUtil.isMobileNumber(query)) {
PromptManager.getInstance().showToast("please input valid phone");
return false;
}
}
boolean b = !TextUtils.isEmpty(searchViewQueryTextEvent.queryText().toString());
return b;
}
})
.switchMap(new Func1<SearchViewQueryTextEvent, Observable<BaseResponseWrapper<SearchUserResponse>>>() {
#Override
public Observable<BaseResponseWrapper<SearchUserResponse>> call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
// Why run in the main thread here
// 2016/6/12 reset api request
String res = searchViewQueryTextEvent.queryText().toString();
return RetrofitManager.newInstance().getApi().searchUserByPhone(res);
}
})
// switch io thread
.subscribeOn(Schedulers.io())
.map(new Func1<BaseResponseWrapper<SearchUserResponse>, List<SearchUserResponse>>() {
#Override
public List<SearchUserResponse> call(BaseResponseWrapper<SearchUserResponse> fuzzyUserRes) {
// some code here
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<SearchUserResponse>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
LogUtils.w("end thread:" + Thread.currentThread().getName());
LogUtils.w("e :" + e);
**//there throw exception android.os.NetworkOnMainThreadException**
}
#Override
public void onNext(List<SearchUserResponse> fuzzyUsers) {
updateUI(fuzzyUsers);
}
});
When I input to the searchview , onError method will throw android.os.NetworkOnMainThreadException.
I have been in the
After switchmap switch to the IO thread.
I use rxjava version :
'io.reactivex:rxjava:1.1.5'.
what can I do?
You can't call subscribeOn twice in the same stream. Only the first will count.
Your code should look like this:
mSubscription = RxSearchView.queryTextChangeEvents(mSearchView)
.debounce(600, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.filter(new Func1<SearchViewQueryTextEvent, Boolean>() {
#Override
public Boolean call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
String query = searchViewQueryTextEvent.queryText().toString();
if (!TextUtils.isEmpty(query) && query.length() >= 11) {
if (!CommonUtil.isMobileNumber(query)) {
PromptManager.getInstance().showToast("please input valid phone");
return false;
}
}
boolean b = !TextUtils.isEmpty(searchViewQueryTextEvent.queryText().toString());
return b;
}
})
// switch io thread
.observeOn(Schedulers.io())
.switchMap(new Func1<SearchViewQueryTextEvent, Observable<BaseResponseWrapper<SearchUserResponse>>>() {
#Override
public Observable<BaseResponseWrapper<SearchUserResponse>> call(SearchViewQueryTextEvent searchViewQueryTextEvent) {
// Why run in the main thread here
// 2016/6/12 reset api request
String res = searchViewQueryTextEvent.queryText().toString();
return RetrofitManager.newInstance().getApi().searchUserByPhone(res);
}
})
.map(new Func1<BaseResponseWrapper<SearchUserResponse>, List<SearchUserResponse>>() {
#Override
public List<SearchUserResponse> call(BaseResponseWrapper<SearchUserResponse> fuzzyUserRes) {
// some code here
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<SearchUserResponse>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
LogUtils.w("end thread:" + Thread.currentThread().getName());
LogUtils.w("e :" + e);
**//there throw exception android.os.NetworkOnMainThreadException**
}
#Override
public void onNext(List<SearchUserResponse> fuzzyUsers) {
updateUI(fuzzyUsers);
}
});
As long as observeOn is a downstream operator, you should use them to switch over threads many times.
Hope that it helps.
Here is the example:
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
#Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
#Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
It's easy to observe courses, but what if I want to print out the pair of student-course? Is that necessary to change Observable<Course> to Observable<Pair<Student, Course>>? This method could be very tedious, because we could have multiple operators during transforming, but we don't want to keep the Pairs all the way transforming the observable.
Is that any other good way we can refer to the original item based on the transformed one?
Depending on your goals and implementation you can use an overloaded variant of flatMap like this:
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
#Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
}, new Func2<Student, Course, Void>() {
#Override
public Void call(Student student, Course course) {
Log.d(tag, student + "->" + course.getName());
return null;
}
})
.subscribe();
students.doOnNext(new Action1<String>() {
#Override
public void call(String student) {
ArrayList<String> studentCourses = courses.get(student);
for (int i=0 ; i<studentCourses.size() ; i++) {
System.out.println(student + " -> " + studentCourses.get(i) + " " + test);
}
}
})
.subscribe(new Subscriber<String>() {
#Override
public void onNext(String s) { /*System.out.println(s); */}
#Override
public void onCompleted() { }
#Override public void onError(Throwable e) { }
});
I've two RxJava Observables and I get an arraylist from first observable and then use it to get data from another observable like this.
Observable<KarobarTvVod> observable1 = youtubeDataHelper.getTVData();
observable1.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<KarobarTvVod>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(KarobarTvVod karobarTvVod) {
Log.d(TAG, "onNext: size" + karobarTvVod.getEtag());
tvObjArrayList = new ArrayList<TVObj>();
for (int i = 0; i < karobarTvVod.getItems().size(); i++) {
TVObj tvObj = new TVObj();
tvObj.setVideoDate(karobarTvVod.getItems().get(i).getSnippet().getPublishedAt());
tvObj.setVideoIcon(karobarTvVod.getItems().get(i).getSnippet().getThumbnails().getHigh().getUrl());
tvObj.setVideoTitle(karobarTvVod.getItems().get(i).getSnippet().getTitle());
tvObj.setVideoID(karobarTvVod.getItems().get(i).getId().getVideoId());
tvObjArrayList.add(tvObj);
}
}
});
Observable<YoutubeViews> observable2 = youtubeDataHelper.getTVDataViews(tvObjArrayList);
observable2.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<YoutubeViews>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError: in 2nd obs");
e.printStackTrace();
}
#Override
public void onNext(YoutubeViews youtubeViews) {
Log.d(TAG, "onNext: views" + youtubeViews.getEtag());
viewsList = new ArrayList<String>();
for (int i = 0; i < youtubeViews.getItems().size(); i++) {
viewsList.add(youtubeViews.getItems().get(i).getStatistics().getViewCount());
}
tvView.displayList(tvObjArrayList, viewsList);
}
});
This is just sample code, I need to pass the tvObjArrayList when it gets populated from 1st Observable to 2nd Observable, what is the best practice to do so ? And also I'm using for-loop inside the 1st Observable, is there a better way to achieve it using rxjava ? Thanks
You should use flatMap operator. It won't get much easier than that.
Observable<KarobarTvVod> observable1 = youtubeDataHelper.getTVData();
observable1.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.flatMap(new Func1<KarobarTvVod, Observable<YoutubeViews>>() {
#Override
public Observable<YoutubeViews> call(KarobarTvVod karobarTvVod) {
Log.d(TAG, "onNext: size" + karobarTvVod.getEtag());
tvObjArrayList = new ArrayList<TVObj>();
for (int i = 0; i < karobarTvVod.getItems().size(); i++) {
TVObj tvObj = new TVObj();
tvObj.setVideoDate(karobarTvVod.getItems().get(i).getSnippet().getPublishedAt());
tvObj.setVideoIcon(karobarTvVod.getItems().get(i).getSnippet().getThumbnails().getHigh().getUrl());
tvObj.setVideoTitle(karobarTvVod.getItems().get(i).getSnippet().getTitle());
tvObj.setVideoID(karobarTvVod.getItems().get(i).getId().getVideoId());
tvObjArrayList.add(tvObj);
}
return youtubeDataHelper.getTVDataViews(tvObjArrayList);
}
}).subscribe(new Subscriber<YoutubeViews>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError: in 1st or 2nd obs");
e.printStackTrace();
}
#Override
public void onNext(YoutubeViews youtubeViews) {
Log.d(TAG, "onNext: views" + youtubeViews.getEtag());
viewsList = new ArrayList<String>();
for (int i = 0; i < youtubeViews.getItems().size(); i++) {
viewsList.add(youtubeViews.getItems().get(i).getStatistics().getViewCount());
}
tvView.displayList(tvObjArrayList, viewsList);
}
});
You can use operator
toList().flatMap()
for Observable A, and in flatMap function, do work for Observable B.
For example:
observableA
.toList()
.flatMap(observableB.subscribe())
.subscribe()
I assume that getTVData and getTVDataViews each emits one item and than calls onComplete. If it true, than the following example works. No loop's, just pure rx :)
//getTVData should emit one item and then call obComplete
//otherwise toList() will wait forever
service.getTVData()
.flatMap(karobarTvVod -> Observable.from(karobarTvVod.getItems()))
.map(item -> {
TVObj tvObj = new TVObj();
//set other fields
//by the way, I recommend you to use immutable objects
return tvObj;
})
.toList()
//here we have List<TVObj>
.flatMap(
objs -> {
//getTVDataViews should emit one item and then call onComplete
//otherwise toList will wait forever
return service.getTVDataViews(objs)
.flatMap(youtubeViews -> Observable.from(youtubeViews.getItems()))
.map(integer -> integer.toString())
//after that we will have List<String>
.toList();
},
//a function that combines one item emitted by each of the source and collection Observables
// and returns an item to be emitted by the resulting Observable
new Func2<List<TVObj>,List<String>,Pair<List<TVObj>,List<String>>>() {
#Override
public Pair<List<TVObj>, List<String>> call(List<TVObj> objs, List<String> strings) {
return new Pair(objs, strings);
}
})
.subscribe(pair -> tvView.displayList(pair.first, pair.second));
PS. While this approach is more concise, I believe that loop for creating list of items is more efficient.
You need to subscribe the second observable in the onComplete of the first one
Observable<KarobarTvVod> observable1 = youtubeDataHelper.getTVData();
Observable<YoutubeViews> observable2 = youtubeDataHelper.getTVDataViews(tvObjArrayList);
observable1.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<KarobarTvVod>() {
#Override
public void onCompleted() {
observable2.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<YoutubeViews>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError: in 2nd obs");
e.printStackTrace();
}
#Override
public void onNext(YoutubeViews youtubeViews) {
Log.d(TAG, "onNext: views" + youtubeViews.getEtag());
viewsList = new ArrayList<String>();
for (int i = 0; i < youtubeViews.getItems().size(); i++) {
viewsList.add(youtubeViews.getItems().get(i).getStatistics().getViewCount ());
}
tvView.displayList(tvObjArrayList, viewsList);
}
});
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(KarobarTvVod karobarTvVod) {
Log.d(TAG, "onNext: size" + karobarTvVod.getEtag());
tvObjArrayList = new ArrayList<TVObj>();
for (int i = 0; i < karobarTvVod.getItems().size(); i++) {
TVObj tvObj = new TVObj();
tvObj.setVideoDate(karobarTvVod.getItems().get(i).getSnippet().getPublishedAt());
tvObj.setVideoIcon(karobarTvVod.getItems().get(i).getSnippet().getThumbnails().getHigh().getUrl());
tvObj.setVideoTitle(karobarTvVod.getItems().get(i).getSnippet().getTitle());
tvObj.setVideoID(karobarTvVod.getItems().get(i).getId().getVideoId());
tvObjArrayList.add(tvObj);
}
}
});
Of course to make this code more readable I would use a Consumer function for the observable2.subsriberOn on the onComplete method