I have a really tricky problem.
I have a method of void insert(User user) in my DAO. I wrapped it with :
public Completable insertUser(User user) {
return Completable.fromAction(() -> userDao.insert(user))
.subscribeOn(Schedulers.io())
which returns a Completable.
In my ViewModel, I just return the same thing:
public Completable insertUser(User user) {
return userDao.insertUser(user);
And in my UI, I observe on the completable :
vm.insertUser(vm.getSomeUsers())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
// Do nothing
}
#Override
public void onComplete() {
userAdapter.populate( ? ) // here I need the refreshed user
}
#Override
public void onError(Throwable e) {
}
});
The problem is that my insert does not return the updated user, I cannot change it to Observable<User> insertUser() because I'm using a lower version of Room.
So my question is, how can I populate the adapter with the updated user ? I have another method in my dao Flowable<User> getUsers(), but I have to use another observer to retrieve the users, so that leads to nested observers.
What is the best way to do it ?
You can do it as simple as
public Single<User> insertUser(User user) {
return Completable.fromAction(() -> userDao.insert(user))
.subscribeOn(Schedulers.io())
.andThen(Single.just(user));
It will return user you just inserted.
Related
I am new to RXJava2, so need some help, I have to implement the functionality of favourite, when user clicks on fav then it's inserted in the greendo database and when he unfavourite the same event then it's removed from the greendo database.
I am able to insert and fetch the result but not getting the idea how to remove it.
This line in the below code returning me void mDaoSession.getFavouriteDao().deleteByKey(favourite.getId());
It's saying incompatible type then I how to make observable compatible with void return type.
#Override
public Observable<Long> deleteFavouriteEvent(Favourite favourite) {
return Observable.fromCallable(new Callable<Long>() {
#Override
public Long call() throws Exception {
return mDaoSession.getFavouriteDao().deleteByKey(favourite.getId());
}
});
}
Insertion working fine :
#Override
public Observable<Long> insertFavouriteEvent(Favourite favourite) {
return Observable.fromCallable(new Callable<Long>() {
#Override
public Long call() throws Exception {
return mDaoSession.getFavouriteDao().insert(favourite);
}
});
}
You can use Completable.fromAction instead of Observable.
public Completable deleteFavouriteEvent(Favourite favourite) {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
mDaoSession.getFavouriteDao().deleteByKey(favourite.getId());;
}
});
}
#Delete / #Insert / #Update all of them takes Collection of Entity or the Entity itself.
You have to directly pass favourite object instead of favourite.getId()
And also scheduling a function with void as return type use Completable instead of Observable / Flowable.
I am a beginner for rxjava,room and mvvm architecture.I am trying to fetch a user from room database, using Rxjava.
when I get user successfully I want to show a toast and start another activity. and in case of failure, I will show an error message in text input layout.
I have try to do that using following code.
in my Activity, I have a method authorizeUser() which is called on button click.
private void authorizeUser() {
loginViewModel.checkInDb()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Log.e("Action", "Complete");
showToast();
startAnotherActivity();
}
#Override
public void onError(Throwable e) {
Log.e("Action", "error");
showError();
}
});
}
In my ViewModel
public Completable checkInDb() {
Completable completable= Completable.fromAction(() ->
userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
return completable;
}
in UserDataSource class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
Using above code, onComplete method is always called in activity, weather in view model, user fetched successfully or not
But what I want to do is, I want to throw an error or send a notification to the activity when there is error thrown (or on Error method is called in viewmodel). so that I can display error on my activity.
May be my question can be silly, but I am new to this. Please help me.
Although it would be better to maybe wrap the response in LiveData so you would get the subscription/lifecycle "for free", unless there is a specific need to have a completable on the UI, which I don't really any reason for it).
I would instead change your Dao to return Observable instead of Single, and then (one again this is not the optimal solution, should wrap into LiveData) you can return that Observable to the UI:
public Observable checkInDb() {
return userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
}
And change your Daos
in UserDataSource class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
So now your view can checkInDb() and handle these cases.
PS: I'm assuming this daoAccess is not an API call rather a local DB (probably Room).
I'm new into rxJava and it's making my head spin. Basically I'm pulling data from youtube api with retrofit which gives back Observable and with youtubeDataMapper I'm mappng it into Youtube Pojo object which contains String videoID. So my question is, how to make this method return that string instead of Completable?
This is my method:
#Override
public Completable downloadVideoUrl(String query) {
addSubscription(youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler)
.subscribe());
return Completable.complete();
}
You have two choices:
Make your downloadVideoUrl return Observable instead of Completable:
Preferred way:
#Override
public Completable downloadVideoUrl(String query) {
return youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler);
}
Notice lack of subscribe operator here.
Then wherever you want to get videoId:
downloadVideoUrl(query)
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String videoId) {
// do whatever you want with videoId
}
});
Use toBlocking().first()
This is not preffered as you block current Thread until Observable finishes
#Override
public String downloadVideoUrl(String query) {
return youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(subscribeScheduler)
.observeOn(observeScheduler)
.toBlocking().first();
}
First of all, it is better to make Retrofit return Single instead of Observable because you are expecting a single server response (and not a sequence of responses).
Secondly, Completable.complete() is a factory method for a Completable that does nothing at all. So you don’t need it here.
Regarding String videoID, it depends on what you are planning to do with it. Also, I have no idea what your .addSubscription() is doing.
I would suggest doing something like the following:
class YourClass {
private final CompositeSubscription compositeSubscription = new CompositeSubscription();
// you must call compositeSubscription.clear() either in class .finalize() or on some UI lifecycle event
void yourMethod() {
final Single videoID = youtubeApi.getYoutubeId(query, Constants.youtubeApi)
.map(youtubeDataMapper::map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
final Subscription subscription = videoID.subscribe(new SingleSubscriber() {
#Override
public void onSuccess(String value) {
// TODO: do whatever with the value
}
#Override
public void onError(Throwable error) {
// TODO: log and/or display error
}
});
compositeSubscription.add(subscription);
}
}
I'm using retrofit2 with Rx. I have two API calls. If first call returns empty response with code 400 I need to make second API call, if not then just to show result. I've implemented custom error handling how shown here. Here is my solution:
getResponse1(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverErrorImpl<Response1, BaseError>(BaseError.class) {
#Override
public void onNext(Response1 response) {
view.onSuccess(response);
}
#Override
public void onClientError(BaseError response) {
getResponse2(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverErrorImpl<Response2, BaseError>(BaseError.class) {
#Override
public void onNext(Response2 response) {
view.onSuccess(response);
view.hideProgress();
}
#Override
public void onError(Throwable throwable) {
super.onError(throwable);
view.hideProgress();
}
});
}
#Override
public void onError(Throwable throwable) {
super.onError(throwable);
view.hideProgress();
}
});
Is it possible to simplify this code that goes inside method onClientError? Is it good solution to like that?
1). To simplify it, it would be better that Response1 and Response2 will extend some base class, so in your chain, you can operate with base class, which can be casted to certain type when needed
So, let's assume, that you have BaseResponse:
public abstract class BaseResponse{
public static int TYPE_RESPONSE_1 = 1;
public static int TYPE_RESPONSE_2 = 2;
public abstract int getType(); //every class MUST override this method
}
Response1 and Response2 should override BaseResponse
2). getResponse1 and getResponse2 should return Observable<BaseResponse>
3). Your target code:
getResponse1(token)
.onErrorResumeNext(new Func1<Throwable, Observable<BaseResponse>>() {
#Override
public Observable<BaseResponse> call(Throwable throwable) {
// I use Retrofit 1.9
// And in Retrofit 1.9 I have class RetrofitError, which may provide me all info about error
// I'm absolutelly sure Retrofit 2 also have such class,
// but it may have another name
if(/* is your target error */){
//cast it tour target error
return getResponse2(token);
}
return Observable.error(throwable);
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverErrorImpl<Response1, BaseError>(BaseError.class) {
#Override
public void onNext(BaseResponse response) {
if(response.getType() == BaseResponse.TYPE_RESPONSE_1){
view.onSuccess(response);
} else {
view.onSuccess(response);
view.hideProgress();
}
}
#Override
public void onError(Throwable throwable) {
super.onError(throwable);
view.hideProgress();
}
});
Jeesh, why does everyone make it so complicated? In practice I've felt that every time I needed to subscribe to an Observable in the subscribe of another, there is an operator that will do it much more cleanly for me:
<T,E extends Throwable> Observable<T>
whenExceptionIs(Class<E> what, Func1<E,Observable<T>> result) {
return t -> {
return what.isInstance(t) ? result.call(t) : Observable.error(t);
};
}
getResponse1(token)
.onErrorResumeNext(whenExceptionIs(BaseError.class, getResponse2(token)))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(view::onSuccess, view::hideProgress, err -> view.hideProgress());
If you have special error handling needs, create a custom Subscriber that handles that, but make sure that error handling in general is
handled in the Observable chain if it can do anything about it (ignore it, retry the calls etc)
propagated downstream.
I have to execute 3 API calls in the sequence and to do so
I use observable.concatMap(new Func1<>...)
and at the last one I have a subscriber to change activity
However I want to update progressBar in UI thread to let user know that part of task is done.
private void getAllData() {
updateUserTask(
getUserScheduleObservable(
getCurrentUserObservable()));
}
private void updateUserTask(Observable<TaskWrapper> observable) {
wrapObservable(observable)
.subscribe(new Subscriber<TaskWrapper>() {
#Override
public void onNext(TaskWrapper taskWrapper) {
openCurrentFragment();
hideProgressIndicators();
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
}
});
}
private Observable<TaskWrapper> getUserScheduleObservable(Observable<ScheduleWrapper> observable) {
return observable.concatMap(
scheduleWrappers1 -> apiManager.getRouteObservable(vehicleDeliveryAreaRiderBundle.getVehicle().getId()));
}
private Observable<ScheduleWrapper> getCurrentUserObservable() {
return apiManager.getUserObservable().concatMap(
user -> apiManager.getCurrentScheduleObservable()
);
}
I think that you are looking for something like this.
public class ExampleUnitTest {
#Test
public void testSample() throws Exception {
Observable<String> first = Observable.just("First");
Observable<String> second = Observable.just("Second");
Observable<String> third = Observable.just("Third");
Observable.concat(first, second, third)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(this::updateProgress)
.subscribe();
}
private void updateProgress(String s) {
System.out.println(String.format("Notify your progress that %s ended", s));
}
}
Just concatenating those observables, you can achieve the expected result.
Hope that it helps.
Best regards.