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
Related
I am trying to use RXjava to get data from an Arraylist in Android
public ArrayList<String> SelecIDValueGetterObservable(Context mContext) {
ArrayList<String> SelectedIds = new ArrayList<>();
CompositeDisposable mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(Observable.fromCallable(() -> SelecetIDValuegetter(mContext))
.subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<ArrayList<String>>() {
#Override
public void onNext(ArrayList<String> IdObsList) {
Toast.makeText(mContext,
"size " + IdObsList.size(), Toast.LENGTH_LONG).show();
for (int i = 0; i < IdObsList.size(); i++) {
SelectedIds.add(IdObsList.get(i));
}
}
#Override
public void onError(Throwable e) {
FirebaseCrashlytics.getInstance().recordException(e);
}
#Override
public void onComplete() {
// mCompositeDisposable.dispose();
}
})
);
return SelectedIds;
}
the SelecetIDValuegetter(mContext) is Like this
public ArrayList<String> SelecetIDValuegetter(Context mContext) {
ArrayList<String> SelectedIds = new ArrayList<>();
SelectedIds .add("A");
SelectedIds .add("B");
SelectedIds .add("C");
SelectedIds .add("D");
return SelectedIds ;
}
If SelecetIDValuegetter emits data normally, hwoever It deosnot work In Rxjava, how can we do it
If you want to use Observable, you should not return list directly as Observable is async, instead return Observable and subscribe to receive the data
public Observable<List<String>> SelecIDValueGetterObservable(Context mContext) {
return Observable.fromCallable(() -> SelecetIDValuegetter(mContext));
}
//at receiver end
SelecIDValueGetterObservable(this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(IdObsList -> {
//ypu will get the list here
});
On a side note, Not sure about your need for using Observable in between SelecetIDValuegetter, you can directly return the list instead of using an Observable as you are not doing heavy work in method SelecetIDValuegetter AFAIK.
I tried to loop an Api call based on an Array size, and put the result into an adapter. But I get null when I tried to use the results of the api call because it is outside a subscribe. how do I use the variable outside the subscribe?
Here's my Code
private void getMoviesData(final String lang) {
favouriteMovies = databaseHelper.getAllFavouriteMovies();
for (int i = 0; i < favouriteMovies.size(); i++) {
api.getSingleMovie(favouriteMovies.get(i).getMovieId(),apiKey, lang)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<SingleMovieResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
progressBar.setVisibility(View.GONE);
Log.d(TAG, "Show Data Error!: " + e);
}
#Override
public void onNext(SingleMovieResponse singleMovieResponse) {
for (int i = 0; i < singleMovieResponse.getSpoken_languages().size(); i++ ) {
MoviesObject moviesObject = new MoviesObject();
moviesObject.setMovieId(singleMovieResponse.getId());
moviesObject.setTitle(singleMovieResponse.getTitle());
moviesObject.setPosterPath(singleMovieResponse.getPoster_path());
moviesObject.setOverview(singleMovieResponse.getOverview());
moviesObject.setReleaseDate(singleMovieResponse.getRelease_date());
moviesObject.setVoteAverage(singleMovieResponse.getVote_average());
if (singleMovieResponse.getOverview().equals("")) {
moviesObject.setOverview(Objects.requireNonNull(getActivity()).getResources().getString(R.string.no_overview_movie));
}
movies.add(moviesObject);
}
}
});
}
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
MovieAdapter movieAdapter = new MovieAdapter(movies,getActivity());
recyclerView.setAdapter(movieAdapter);
recyclerView.setHasFixedSize(true);
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
how to get movies values outside the subscribe?
You don't. The result from the stream is asynchronous, which means that it will be called some time later, while you setting the adapter with data is synchronous. Also, don't use a loop when handling streams, use the Reactive operators as shown below. I can't compile to test whether it works, but below is roughly the correct answer to your question.
private void getMoviesData(final String lang) {
favouriteMovies = databaseHelper.getAllFavouriteMovies();
Observable.fromIterable(favouriteMovies) // Creates a stream of Favourite Movies
.flatMap(new Function<FavouriteMovie, ObservableSource<?>>() { // Makes a request for each FavouriteMovie
#Override
public ObservableSource<?> apply(FavouriteMovie favouriteMovie) throws Exception {
return api.getSingleMovie(favouriteMovie.getMovieId(), apiKey, lang);
}
})
.map(new Function<SingleMovieResponse, MoviesObject>() { // Maps the response to the wanted object
#Override
public MoviesObject apply(SingleMovieResponse singleMovieResponse) throws Exception {
return map(singleMovieResponse);
}
})
.toList() // Merges each item of the Observable stream into a Single stream as List<Object>
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new SingleObserver<List<MoviesObject>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(List<MoviesObject> moviesObjects) {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
movieAdapter.setList(moviesObjects); // Create this method in the adapter to update list
movieAdapter.notifyDataSetChanged();
}
#Override
public void onError(Throwable e) {
progressBar.setVisibility(View.GONE);
Log.d(TAG, "Show Data Error!: " + e);
}
});
movieAdapter = new MovieAdapter(movies,getActivity()); // MovieAdapter should be a class field
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(movieAdapter);
}
private MoviesObject map(SingleMovieResponse singleMovieResponse) {
MoviesObject moviesObject = new MoviesObject();
moviesObject.setMovieId(singleMovieResponse.getId());
moviesObject.setTitle(singleMovieResponse.getTitle());
moviesObject.setPosterPath(singleMovieResponse.getPoster_path());
moviesObject.setOverview(singleMovieResponse.getOverview());
moviesObject.setReleaseDate(singleMovieResponse.getRelease_date());
moviesObject.setVoteAverage(singleMovieResponse.getVote_average());
if (singleMovieResponse.getOverview().equals("")) {
moviesObject.setOverview(Objects.requireNonNull(getActivity()).getResources().getString(R.string.no_overview_movie));
}
return moviesObject;
}
P.S. Try introducing lambda expressions to make your life easier, or even better Kotlin!!!
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 want to use RxAndroid in my project,
and i make the thread sleep for 50ms
but it caused anr,the code
public void getTypeAndCommodity() {
Observable.from(getCommodities())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Commodity>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Commodity commodity) {
}
});
}
and the getCommodities:
private ArrayList<Commodity> getCommodities() {
// some test info
ArrayList<Commodity> list = new ArrayList<>();
for (int i = 0; i < 99; i++) {
Commodity commodity = new Commodity();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
commodity.setName("name" + i);
commodity.setType("type" + (i + 1) / 10);
list.add(commodity);
}
return list;
}
why it cause anr?please help
This happens because getCommodities() is executed in main thread, and only the item emited is executed in io thread with subscribeOn(Schedulers.io()). If you want to execute getCommidities() in background thread too, you need to create an observable with defer() method:
Observable.defer(new Func0<Observable<Object>>() {
#Override public Observable<Object> call() {
return Observable.from(getCommodities());
}
}).subscribeOn(Schedulers.io())...
If you need more info: http://blog.danlew.net/2015/07/23/deferring-observable-code-until-subscription-in-rxjava/
I'm using Retrofit to get bookmarks from REST API:
public interface BookmarkService {
#GET("/bookmarks")
Observable<List<Bookmark>> bookmarks();
}
Now I would like to emit each item from this list with delay.
I did something similar to this in Java, but onCompleted is never triggered.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap(new Func1<List<Bookmark>, Observable<Bookmark>>() {
#Override
public Observable<Bookmark> call(List<Bookmark> bookmarks) {
Observable<Bookmark> resultObservable = Observable.never();
for (int i = 0; i < bookmarks.size(); i++) {
List<Bookmark> chunk = bookmarks.subList(i, (i + 1));
resultObservable = resultObservable.mergeWith(Observable.from(chunk).delay(1000 * i, TimeUnit.MILLISECONDS));
}
return resultObservable;
}
})
.observeOn(AndroidSchedulers.mainThread());
}
What I'm doing wrong?
Usage:
mSwipeRefreshLayout.setRefreshing(true);
getBookmarks()
.subscribe(new Observer<Bookmark>() {
#Override
public void onCompleted() {
Timber.i("Completed");
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onError(Throwable e) {
Timber.i("Error: %s", e.toString());
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onNext(Bookmark bookmark) {
Timber.i("Bookmark: %s", bookmark.toString());
mBookmarksAdapter.addItem(bookmark);
}
});
As you use a merge operation, onCompleted will be call if all Observables are completed. but Observable.never() will never complete. Use Observable.empty() instead.
According to your code, your want to emit sublist with delay. The sublist contains only one element
What you can do : flatmap your list, to emit each items. Buffer it to build a list from items, then use a delay.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap((bookmarks) -> Observable.from(bookmarks)
.buffer(1)
.scan(new Pair(0, null), (ac, value) -> new Pair(acu.index + 1, value)
.flatMap(pair -> Observable.just(pair.value).delay(pair.index, SECONDS))
.observeOn(AndroidSchedulers.mainThread());
}
it might work (not tested)