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!!!
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 have ArrayList of strings. For each string, I want to call API with Retrofit2. And response of each API gives some data that I want to use and from there I want to call another API. Finally it will return string whether is success or not. I am able to achieve this but I want to all of the APIs should be called one after the other Like synchronous.
Here is my code looks like.
for (int i = 0; i < domainList.size(); i++) {
feedData3(domainList.get(i));
}
private void feedData3(String domain) {
Domain domain1 = new Domain();
streamFetch(domain)
.concatMap((Function<Contact, ObservableSource<?>>) contact -> {
domain1.setDomainData(contact.getDATA());
domain1.setDomain(domain);
return streamFetchData(domain1.getDomain(), domain1.getDomainData());
})
.subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
disposable = d;
}
#Override
public void onNext(Object o) {
Log.e(TAG, domain + ",Success");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, domain + ",failure = " + e.getLocalizedMessage());
}
#Override
public void onComplete() {
}
});
}
public Observable<Contact> streamFetch(String domain) {
//GET API CALL
}
public Observable<String> streamFetchData(String domainName, String domainData) {
//POST API CALL
}
I'm new in RxJava. I have currently executed three API calls parallel which is independent of each other via Retrofit using Single.Zip Operator. On getting a successful response of all three API calls, I have to insert the data from all three APIs into Room database into Different entities which takes 20 seconds.
So I need to execute database operations inside Single.Zip operator. Because the logic is written inside onSuccess method running away before Database Operation performed.
I have tried to take separate Observer for performing database operation but didn't work.
public void callOfflineDataAPIs() {
setIsLoading(true);
Single<BaseResponse<ProductResponse>> single1 = getDataManager().getOfflineProductListApiCall(getDataManager().getLastTimeStampOfflineProductCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<LocationResponse>> single2 = getDataManager().getOfflineLocationListApiCall(getDataManager().getLastTimeStampOfflineLocationCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<OfflineMasterData>> single3 = getDataManager().getOfflineMasterDataListApiCall(getDataManager().getLastTimeStampOfflineMasterCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribeWith(new DisposableSingleObserver<List<Boolean>>() {
#Override
public void onSuccess(List<Boolean> apiCalls) {
setIsLoading(false);
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess");
boolean isSync = true;
for (int i = 0; i < apiCalls.size(); i++) {
if (!apiCalls.get(i)) {
isSync = false;
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess- apiCalls.get(i)", i);
callOfflineDataAPIs();
break;
}
}
if (isSync) {
LogHelper.e(TAG, "IF-isSync");
if (BuildConfig.IS_CLIENT_BUILD) {
LogHelper.e(TAG, "IF-isSync-IS_CLIENT_BUILD-true");
getDataManager().setCurrentWarehouseKey(1);
getNavigator().onGoButtonClick();
} else {
LogHelper.e(TAG, "ELSE-isSync-IS_CLIENT_BUILD-false");
getWarehouseList();
}
}
}
#Override
public void onError(Throwable e) {
LogHelper.e(TAG, "DisposableSingleObserver- Throwable");
setIsLoading(false);
String errorMessage = new NetworkError(e).getAppErrorMessage();
getNavigator().exitApplicationOnError(errorMessage);
}
});
}
Logic written inside onSuccess Method execute once all DB Operation performed.
You can modify your code to something like:
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io())
.map(new Function<List<Boolean> apiCalls, List<Boolean> apiCalls>() {
#Override
public List<Boolean> apiCalls apply(List<Boolean> apiCalls) throws Exception {
// perform database operations here
return apiCalls;
}
})
.observeOn(getSchedulerProvider().ui())
.subscribe(new Observer<List<Boolean>>() {
#Override
public void onNext(User user) {
// Do something
}
#Override
public void onError(Throwable e) {
// Do something
}
#Override
public void onComplete() {
// Do something
}
});
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
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)