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
}
Related
In my todoApp I've implemented MediatorLiveData for learning purpose in the following manner:
private val todoListMediator = MediatorLiveData<NetworkResult<List<TodoEntity>>>()
private var todoSource: LiveData<NetworkResult<List<TodoEntity>>> =
MutableLiveData()
val todoResponse: LiveData<NetworkResult<List<TodoEntity>>> get() = todoListMediator
viewModelScope.launch(dispatcherProvider.main) {
todoSource =
todoRepository.getTodoList()
todoListMediator.addSource(todoSource) {
todoListMediator.value = it
}
}
The above code works fine. Now I wanna make following changes and I don't have clear picture how can I achieve them.
As soon as todoListMediator.addSource() observes the todoList:
1] I want to iterate over that original Todo list and make a network call for each element and add some more field to them.
2] I wanna save the new todo list(where each Todo now has some extra field we received by network call in step 1)
3] and finally, I want to assign that new todo list(with extra field) to the todoListMediator.
// sudo to illustrate the above scenario
viewModelScope.launch(dispatcherProvider.main) {
//step.1 get original todo list
todoListMediator.addSource(todoSource) { it ->
// step 2. iterate over original todo list from step 1 and make network call to get extra field for each element and update the original list
//for example
val newFieldForTodoElement = NetworkResult<PendingTodoValues?> = todoRepository.getPendingTodoValues()
// step 3. one all the original list is updated with new field values, save the Todo list in local db
// step 4. Then pass the todo list with new fields to mediator live data from db
todoListMediator.value = it
}
}
Any tricks with detailed explanation on code will be a great help for my learning. Thanks!
you can use RXjava with Flatmap operator
something like that may help ?
getPostsObservable()
.subscribeOn(Schedulers.io())
.flatMap(new Function<Post, ObservableSource<Post>>() {
#Override
public ObservableSource<Post> apply(Post post) throws Exception {
return getCommentsObservable(post);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Post>() {
#Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
#Override
public void onNext(Post post) {
updatePost(post);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
#Override
public void onComplete() {
}
});
}
private Observable<Post> getPostsObservable(){
return ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<List<Post>, ObservableSource<Post>>() {
#Override
public ObservableSource<Post> apply(final List<Post> posts) throws Exception {
adapter.setPosts(posts);
return Observable.fromIterable(posts)
.subscribeOn(Schedulers.io());
}
});
}
private void updatePost(final Post p){
Observable
.fromIterable(adapter.getPosts())
.filter(new Predicate<Post>() {
#Override
public boolean test(Post post) throws Exception {
return post.getId() == p.getId();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Post>() {
#Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
#Override
public void onNext(Post post) {
Log.d(TAG, "onNext: updating post: " + post.getId() + ", thread: " + Thread.currentThread().getName());
adapter.updatePost(post);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
#Override
public void onComplete() {
}
});
}
private Observable<Post> getCommentsObservable(final Post post){
return ServiceGenerator.getRequestApi()
.getComments(post.getId())
.map(new Function<List<Comment>, Post>() {
#Override
public Post apply(List<Comment> comments) throws Exception {
int delay = ((new Random()).nextInt(5) + 1) * 1000; // sleep thread for x ms
Thread.sleep(delay);
Log.d(TAG, "apply: sleeping thread " + Thread.currentThread().getName() + " for " + String.valueOf(delay)+ "ms");
post.setComments(comments);
return post;
}
})
.subscribeOn(Schedulers.io());
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!!!
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
}
});
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 am using Realm + Retrofit2
I am trying to implement following :
UI asks DataManger for data.
DataManger returns cached data, and checks if data has expired then calls for fresh data.
When fresh data is saved in Realm NetworkManager triggers event which is captured by UI for updating data.
Issue
When NetworkHelper saves the data in Realm, after commitTransaction() due to onChangeListeners of RealmObjects, the code in DataManger onCall() part is executed again, which again calls NetworkHelper for new data, which subsequently again saves data from the network and process goes into infinite loop. I tried gitHubUser.removeChangeListeners() at multiple points but it still not working. Please point out anything fundamentally being wrong or the correct way to implement with Realm.
Implemented codes are as follows:
DataManager
public Observable<GitHubUser> getGitHubUser(final String user){
return databaseHelper.getGitHubUser(user).doOnNext(new Action1<GitHubUser>() {
#Override
public void call(GitHubUser gitHubUser) {
if(gitHubUser==null || !isDataUpToDate(CACHE_TIME_OUT,gitHubUser.getTimestamp())){
if(gitHubUser!=null)
System.out.println("isDataUpToDate = " + isDataUpToDate(CACHE_TIME_OUT,gitHubUser.getTimestamp()));
networkHelper.getGitHubUserRxBus(user);
}
}
});
}
DataBaseHelper
public Observable<GitHubUser> saveGitHubUser(GitHubUser user, String userId) {
realmInstance.beginTransaction();
user.setUserId(userId);
user.setTimestamp(System.currentTimeMillis());
GitHubUser userSaved = realmInstance.copyToRealm(user);
Observable<GitHubUser> userSavedObservable = userSaved.asObservable();
realmInstance.commitTransaction();
return userSavedObservable;
}
public Observable<GitHubUser> getGitHubUser(String user){
System.out.println("DatabaseHelper.getGitHubUser");
GitHubUser result = realmInstance.where(GitHubUser.class).contains("userId",user, Case.INSENSITIVE).findFirst();
if(result != null){
return result.asObservable();
}else{
return Observable.just(null);
}
}
NetworkHelper
public void getGitHubUserRxBus(final String user){
System.out.println("NetworkHelper.getGitHubUserRxBus");
retroFitService.user(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<GitHubUser, Observable<GitHubUser>>() {
#Override
public Observable<GitHubUser> call(GitHubUser gitHubUser) {
System.out.println("NetworkHelper.call");
return databaseHelper.saveGitHubUser(gitHubUser,user);
}
}).subscribe(new Action1<GitHubUser>() {
#Override
public void call(GitHubUser gitHubUser) {
if (rxBus.hasObservers()) {
System.out.println("NetworkHelper.call");
rxBus.send(gitHubUser);
}
}
});
}
Activity
subscription.add(dataManager.getGitHubUser("gitHubUserName")
.subscribe(new Subscriber<GitHubUser>() {
#Override
public void onCompleted() {
System.out.println("LoginActivity.call" + " OnComplete");
}
#Override
public void onError(Throwable e) {
System.out.println("throwable = [" + e.toString() + "]");
}
#Override
public void onNext(GitHubUser gitHubUser) {
System.out.println("LoginActivity.call" + " OnNext");
if (gitHubUser != null) {
sampleResponseText.setText(gitHubUser.getName() + " timestamp " + gitHubUser.getTimestamp());
}
onCompleted();
}
}));
subscription.add(rxBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
if(o instanceof GitHubUser){
GitHubUser gitHubUser = ((GitHubUser)o);
sampleResponseText.setText(gitHubUser.getName() + " time " + gitHubUser.getTimestamp());
}
}
}));
UPDATE
Finally Solved it by following in DataManger:
return Observable.concat(databaseHelper.getGitHubUser(user).take(1),
networkHelper.getGitHubUser(user))
.takeUntil(new Func1<GitHubUser, Boolean>() {
#Override
public Boolean call(GitHubUser gitHubUser) {
boolean result = gitHubUser!=null && isDataUpToDate(CACHE_TIME_OUT,gitHubUser.getTimestamp());
System.out.println("isDataUpToDate = " + result);
return result;
}
});
I think you have a loop going in your code:
1) You create an observable from a RealmResults in getGithubUser().Realm observables will emit every time you change data that might effect them.
2) You call networkHelper.getGitHubUserRxBus(user) after retrieving the user from Realm.
3) When getting a user from the network, you save it to Realm, which will trigger the Observable created in 1) to emit again, which creates your cycle.
To break it, you can do something like result.asObservable().first() in getGitHubUser() as that will only emit once and then complete, but it depends on your use case if that is acceptable.