RxAndroid call one network call after another - android

I am new to RxJava. I have a scenario where I want to call first login webservice (getLoginObservable) and on success, want to call another webservice (getFetchDataObservable) to get user information.
I have following code working if login is success. But I am unable to figure out how to code failure case.
private void doLogin() {
emailAddress = editTextUsername.getText().toString();
final String password = editTextPassword.getText().toString();
showProgress(null, getString(R.string.loggingInPleaseWait));
getLoginObservable(editTextUsername.getText().toString(), password)
.map(response -> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
}
return response;
})
.flatMap(response -> {
return getFetchDataObservable();
})
.subscribe(res -> {
dismissProgress();
if (res.result) {
saveInformation(password, res);
} else {
ConstantsMethods.showOkButtonDialog(getContext(), res.message, null);
}
}, e -> {
dismissProgress();
if (e instanceof NoInternetConnectionException) {
ConstantsMethods.showOkButtonDialog(getContext(), getString(R.string.noInternetConnection), null);
}
Log.e(LoginFragment.class.getSimpleName(), e.getMessage());
});
}
private Observable<WsResponse<SecurityQuestion>> getLoginObservable(String userName, String password) {
return Observable.<WsResponse<SecurityQuestion>>create(subscriber -> {
getPresenter().doLogin(getActivity(), userName, password, appType,
new Callback<Void, WsResponse<SecurityQuestion>>() {
#Override
public Void callback(final WsResponse<SecurityQuestion> param) {
subscriber.onNext(param);
return null;
}
});
});
}
private Observable<WsResponse<PatientDataProfile>> getFetchDataObservable() {
return Observable.create(subscriber -> {
new AfPatientsPresenter().fetchPatientData(getContext(), emailAddress, "", new Callback<Void, WsResponse<PatientDataProfile>>() {
#Override
public Void callback(WsResponse<PatientDataProfile> param1) {
subscriber.onNext(param1);
subscriber.onComplete();
return null;
}
});
});
}
As much i know RxJava, I can figure out that getLoginObservable(editTextUsername.getText().toString(), password) observable send response to map (map(response -> { ... }) and this map return response to flatmap (flatMap(response -> { ... }) and its response is sent to subscriber. Here i am just lost that how can i skip (second network call)flatmap flatMap(response -> { ... } to send response directly to subscriber in case of login failure.

instead of:
.map(response -> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
}
return response;
})
you can use:
flatMap(response-> {
if (response.result) {
getPresenter().saveUserDetails(getContext(), emailAddress, true, response.dataObject.questionId, response.dataObject.question);
return Observable.just(response);
} else {
return Observable.error(new Exception("Login failed")); // or maybe some LoginFailedException() you can reuse
}
})

Related

RxJava2 make repeatable action and return result depending on if/else logic

Help me please to run rxJava code sample for android. Here is what I want to do:
I have a function, which gives me redirects one by one. I need to take that redirect (if it's not empty) and try to startActivity of external app with it. If redirect empty - I need to break and return some Result data with last redirect found. If this app started successfully - I need to return some Result object that tells that app have been opened, if not opened - I need to try to get next redirect. Here is code that I have:
public Observable<Result> getStartAppResult(String url, AppOpeningRule shopOpeningRule) {
return getSingleHttpRedirectUrl(url, appOpeningRule)
.flatMap(redirect -> {
if(Strings.isNullOrEmpty(redirect)){
return isAppStarted(redirect, appOpeningRule.getPackageName());
} else {
return getDataSourceWithParams(redirect);
}
})
.flatMap(appStarted -> {
if(appStarted){
return getDataSourceWithParams(redirect);
} else {
return getSingleHttpRedirectUrl(url, appOpeningRule);
}
});
}
private ObservableSource<Result> getDataSourceWithParams(String finalUrl) {
Result result = new Result(finalUrl, lastRedirectUrl);
return Observable.just(result);
}
public Observable<Boolean> isAppStarted(String url, String appPackageName) {
return Observable.create(emitter -> {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
intent.setPackage(appPackageName);
boolean hasActivityNotFoundException = false;
try {
context.startActivity(intent);
} catch (ActivityNotFoundException exception) {
hasActivityNotFoundException = true;
} finally {
emitter.onNext(!hasActivityNotFoundException);
emitter.onComplete();
}
});
}
private Observable<String> getSingleHttpRedirectUrl(String url,
final ShopOpeningRule appOpeningRule) {
return httpService.goToWebSiteLink(url)
.compose(rxTransformers.applyUnauthorizedHandlerCustomError())
.onErrorResumeNext(throwable -> {
if (throwable instanceof RetrofitException) {
RetrofitException error = (RetrofitException) throwable;
if (error.getKind() == Kind.HTTP) {
if (error.getHttpExceptionCode() >= REDIRECT_START && error.getHttpExceptionCode() <= REDIRECT_END) {
String redirectUrl = error.getResponse().headers().get(KEY_HEADER_REDIRECT_LOCATION);
if (!Strings.isNullOrEmpty(redirectUrl)) {
lastRedirectUrl = redirectUrl;
if (appOpeningRule.hasRedirectLinkPatterns()) {
if (LinkParser.urlContainsPattern(redirectUrl, appOpeningRule.getRedirectLinkPatterns())) {
String modifiedLink = LinkParser.modifyRedirectLinkIfNeeded(redirectUrl, shopOpeningRule);
return Observable.just(modifiedLink);
} else {
return Observable.just(redirectUrl);
}
} else {
return Observable.just(redirectUrl);
}
}
}
}
}
return Observable.just("");
});
}

How to handle errors from API in Login Activity (Template from Android Studio)

I'm implementing a simple login to an endpoints using Retrofit2. Things work fine when the user credentials are correct but break when I try to enter a non valid data.
I'm trying to handle the errors when the user is not found but I can't find a way to do that.
The error response looks like:
{
"0": [
"erreur",
"statut"
],
"erreur": "Erreur, connexion echoue.",
"statut": "KO"
}
This response has status 200 despite being an error.
The app is crashing with NPE in the LoginRepository where I'm trying to save user's data to SharedPreferences because the error result is not handled so the app threat any response as Successful.
The sample provides a Result class which doesn't seem to work for my use case because the response is always successful:
public class Result<T> {
// hide the private constructor to limit subclass types (Success, Error)
private Result() {
}
#Override
public String toString() {
if (this instanceof Result.Success) {
Result.Success success = (Result.Success) this;
return "Success[data=" + success.getData().toString() + "]";
} else if (this instanceof Result.Error) {
Result.Error error = (Result.Error) this;
return "Error[exception=" + error.getError().toString() + "]";
}
return "";
}
// Success sub-class
public final static class Success<T> extends Result {
private T data;
public Success(T data) {
this.data = data;
}
public T getData() {
return this.data;
}
}
// Error sub-class
public final static class Error extends Result {
private Exception error;
public Error(Exception error) {
this.error = error;
}
public Exception getError() {
return this.error;
}
}
}
And here is how I'm handling the login in the LoginRepository:
public Result<LoggedInUser> login(String username, String password) {
// handle login
Result<LoggedInUser> result = dataSource.login(username, password);
if (result instanceof Result.Success) {
setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
}
return result;
}
Note: I don't have access to the server. I use Gson as converter
The login activity sample I used can be found here
UPDATE:
Login successful with valid credentials:
Check this answer it will help you.
#POST("end_path")
Call<ResponseBody> LoginCall(
#Field("email") String user_id,
#Part("paassword") String language
);
Call<ResponseBody> call = Constant.service.LoginCall(
"email", "pass");
call.enqueue(new retrofit2.Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call,
Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
String responseData = response.body().string();
JSONObject object = new JSONObject(responseData);
if(object.getString("statut").equalsIgnoreCase("success")){
LoggedInUser successData = new
Gson().fromJson(responseData, LoggedInUser.class);
}else{
showToast("Email password incorrect");//or show you want
this message.
}
} catch (IOException e) {
e.printStackTrace();
} catch (JsonSyntaxException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
} else {
showToast("something_went_wrong");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});

RXJava Handle chain api calls: show different error message and continue the stream

I'm trying to convert a callback hell to RX but I'm stuck with getting the proper order, below is my functionality I want to achieve
a) User Login-> get the Auth Cookies, if login credentials invalid show error message
b) use the Auth Cookies to get Customer Type,
c) if the Customer Type is zero/ show profile Restricted Error Message and log out the user
d) if the customerType, not zero proceed to get the other customer Details
e) if any of the customer APIs returns an error response, log out the user and show login failure message
f) if all customer API success show the home screen
API
Login
#FormUrlEncoded
#POST("distauth/UI/Login")
Single<Response<Void>> doLogin1(#Field("username") String username, #Field("password") String password,
#Field("rememberme") String rememberMe, #Field("answer") String answer,
#QueryMap Map<String, String> options);
public Single<Boolean> doLogin(#NonNull String username, #Nullable String password) {
return authapi.doLogin1(username, password, "y", "", logiOptions)
.flatMap(new Function<Response<Void>, SingleSource<Boolean>>() {
#Override
public SingleSource<Boolean> apply(Response<Void> response) throws Exception {
if (response.code() == HttpStatus.MOVED_TEMPORARILY.value()
&& !StringUtils.isEmpty(Session.getCookie())
) {
return Single.just(true);
}
throw new Exception("Invalid Login Details");
}
});
}
//==========
Logout
#FormUrlEncoded
#POST("distauth/UI/Logout")
#Headers("Cache-Control: no-cache")
Completable doLogout(#Field("logout") boolean logout); //return 302 HTTP Status code with empty iPlanetCookie
//==========
NOTE: Loing/logout is not a REST API, this legacy app implement as Form Post ;) so when the success of login return 302 with cookies, and log out also return 302 as status code
Get Customer Details
Single<CustomerAccountVO> getCustomerAccountDetails(boolean forceRefresh);
//==========
Single<CustomerType> getCustomerUserProfile(boolean forceRefresh);
#Override
public Single<CustomerType> getCustomerUserProfile(boolean applyResponseCache) {
return this.mCustomerRemoteDataStore.getCustomerUserProfile(applyResponseCache)
.doOnSuccess(new Consumer<CustomerType>() {
#Override
public void accept(CustomerType customerType) throws Exception {
if (customerType != null && customerType.getBody() != null &&
!StringUtils.isEmpty(customerType.getBody())) {
if (customerType.getBody().equalsIgnoreCase(AppConfig.ERROR)) {
throw new CustomerProfileNotFound(500, "user account restrictions");
} else {
mCustomerLocalRepository.saveCustomerType(customerType);
}
}
}
}).doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e(TAG, "error occurred while getting customer user profile", throwable);
}
});
}
//==========
Single<CustomerAccountId> getAccountId(boolean forceRefresh);
//==========
Single<Customer> getCustomer(boolean forceRefresh);
//==========
Get Customer Full Details
Single<CustomerDetails> getCustomerFullDetails(boolean applyResponseCache);
Implementation:
#Override
public Single<CustomerDetails> getCustomerFullDetails(boolean forceRefresh) {
Single<CustomerDetails> customerDetails = Single.zip(
getCustomerUserProfile(forceRefresh).subscribeOn(Schedulers.io()),
getAccountId(forceRefresh).subscribeOn(Schedulers.io()),
getCustomerAccountDetails(false).subscribeOn(Schedulers.io()),
getCustomer(forceRefresh).subscribeOn(Schedulers.io()), new Function4<CustomerType, CustomerAccountId,
CustomerAccountVO, Customer, CustomerDetails>() {
#Override
public CustomerDetails apply(#NonNull CustomerType customerType,
#NonNull CustomerAccountId customerAccountId,
#NonNull CustomerAccountVO customerAccountVO,
#NonNull Customer customer) throws Exception {
return CustomerDetails.builder().customerType(customerType).customerAccountVO
(customerAccountVO).customer(customer).customerAccountId(customerAccountId).
build();
}
});
return customerDetails;
}
//==========
Each customer request is independent so I thought to execute as sperate thread and zip the final result/
Single<BaseServerResponse> updateCustomerDetails(#Nonnull boolean secure, int secureRequestCode, #Nonnull JSONObject customerContact);
//Presenter Implementation: this implementation not working as i expect above, can some one help me to get this correct,
public void doLoginHandler(#NonNull String username, #NonNull String password) {
checkViewAttached();
getMvpView().showLoadingIndicator();
addSubscription(
apiService.doLogin2(username, password)
.subscribeOn(Schedulers.io())
.flatMap(new Function<Boolean, SingleSource<CustomerDetails>>() {
#Override
public SingleSource<CustomerDetails> apply(Boolean aBoolean) throws Exception {
if (aBoolean) {
//get customr Full Details
Log.d(TAG, "apply: "+aBoolean);
return customerRepository.getCustomerFullDetails(true);
}
return null;
}
}).observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Function<Throwable, SingleSource<? extends CustomerDetails>>() {
#Override
public SingleSource<? extends CustomerDetails> apply(Throwable throwable) throws Exception {
if (throwable instanceof CustomerProfileNotFound) {
getMvpView().showUserProfileAccessRestrictMessage();
} else {
getMvpView().onLoginAuthFailure();
}
return Single.just(CustomerDetails.builder().errorOccurred(true).build());
}
})
.flatMapCompletable(new Function<CustomerDetails, CompletableSource>() {
#Override
public CompletableSource apply(CustomerDetails customerDetails) throws Exception {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout();
}
return Completable.complete();
}
})
.subscribe(new Action() {
#Override
public void run() throws Exception {
getMvpView().onLoginAuthSuccess();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
if (throwable instanceof CustomerProfileNotFound) {
getMvpView().showUserProfileAccessRestrictMessage();
} else {
getMvpView().onLoginAuthFailure();
}
}
}));
}
First I'll state the problem with your code.
.flatMapCompletable(new Function<CustomerDetails, CompletableSource>() {
#Override
public CompletableSource apply(CustomerDetails customerDetails) throws Exception {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout();
}
return Completable.complete();
}
})
This chain observable (which is the one you subscribe to) is always going to give a Completed state unless a network error happens when calling the logout API, that's because you either return the logout Completable or an instant Completable.
Secondly, I think the solution is in logically sorting everything out, the key to error handling in such a case would be creating a different Exception for each error case with it's own error message,
it can go like this (I'm just using the logical names, hopefully that will give you the idea):
loginObservable.flatMap { authCredentials -> {
if (authCredentials.isValid())
return getCustomerTypeObservable(authCredentials)
else
return Single.error(InvalidCredentialsException("message goes here (optional)"))
}}.flatMap { type -> {
if (type == 0)
return Single.error(ProfileRestrictedException("different message maybe?"))
else
return getCustomerDetailsZippedObservable(type)
}}
/* ..etc */
Then at the subscription site you do something like:
myObservable.subscribe( {
/* Handle success*/
}, { exception ->
when(exception) {
is InvalidCredentialsException -> mvpView.showError(message)
is ProfileRestrictedException -> {
mvpView.showError(message)
logout()
}
else -> /* Handle an exception that is not listed above */
}
} )
This way IMO is more convenient than using onErrorResumeNext.
EDIT: You can also overcome the issue stated above by doing something like:
.flatMapCompletable { customerDetails -> {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout()
.then(Completable.error(LoginFailedException("Message"))) /* This will guarantee the stream terminates with the required error type after logout is successful */
} else {
return Completable.complete()
}
}}

returning subscriber in RxJava after storing data fetch from webservice

I am trying to call the web service to fetch the data and storing it into database using following code. I have created a separate class to perform following operation.
Now , the issue is i want to notify my activity when i successfully fetch and store data in database. if some error occurs then i want to show that on UI itself.
somehow i am able to write a code to fetch the data using pagination but not sure how would i notify UI where i can subscribe catch the update related to progress and error if any.
public Flowable<Response> getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(response->response.getSummary().getNext()!=null)
.subscribe(new Subscriber<Response>() {
#Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
#Override
public void onNext(Response response) {
Log.e(TAG, "onNext" );
if(response !=null){
if(response.getFitness()!=null && response.getFitness().size()!=0){
Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.getFitness());
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i(TAG, "onSuccess , fitness data saved");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i(TAG, "onError , fitness data failed to save"+error.getMessage() );
}
});
}else{
Log.i(TAG, "onError , no fitness data available" );
}
}else{
Log.i(TAG, "onError , response is null" );
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError" +t.getMessage());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});;
return null;
}
Updated
Created RxBus to propagate events and error on UI
public class RxBus {
private static final RxBus INSTANCE = new RxBus();
private RxBus(){}
private PublishSubject<Object> bus = PublishSubject.create();
public static RxBus getInstance() {
return INSTANCE;
}
public void send(Object o) {
bus.onNext(o);
}
public void error(Throwable e){bus.onError(e);}
public Observable<Object> toObservable() {
return bus;
}
}
in activity
FitnessRepo fitnessRepo = new FitnessRepo();
fitnessRepo.getFitnessData();
RxBus.getInstance().toObservable().subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
if(o instanceof RealmList ){
RealmList<Fitness> realmList = (RealmList<Fitness>) o;
Log.e(TAG,"Fitness data size "+realmList.size());
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage()+ "");
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Response response= LoganSquare.parse(body.byteStream(),Response.class);
if(response.getErrors() !=null)
if(response.getErrors().size() > 0)
Log.e(TAG, "Error "+response.getErrors().get(0).getErrors());
} catch (IOException t) {
t.printStackTrace();
}
}
}
#Override
public void onComplete() {
}
});
in a web service call
public void getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
request.setEnd_date("2018-07-01T00:00:00");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) {
Log.e(TAG, " Error ");
return;
}
RxBus.getInstance().send(response.getFitness());
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.copyToRealmOrUpdate(response.getFitness());
});
}
}).subscribe(item ->{
},
error ->{
RxBus.getInstance().error(error);
});
}
I have good news for you! You can delete almost all of that code and just make it generally better as a result!
public void fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).subscribe();
}
This method is on a background thread now and returns void, so the way to emit stuff out of this method would be to use either a PublishSubject (one for success, one for failure) or an EventBus.
private PublishSubject<Object> fitnessResults;
public Observable<Object> observeFitnessResults() {
return fitnessResults;
}
public static class Success {
public Success(List<Fitness> data) {
this.data = data;
}
public List<Fitness> data;
}
public static class Failure {
public Failure(Exception exception) {
this.exception = exception;
}
public Exception exception;
}
public void fetchFitnessData() {
...
fitnessResults.onNext(new Success(data));
} catch(Exception e) {
fitnessResults.onNext(new Failure(e));
And then
errors = observeFitnessResults().ofType(Error.class);
success = observeFitnessResults().ofType(Success.class);
There are different ways to achieve this. I will never handle the subscriptions on my own out of a lifecycle scope as it creates a possibility of memory leak. In your case it seems that both success and failure is bound to the UI so you can simply do this.
public Completable fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
return fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).ignoreElements();
}
At UI level, you can just handle your subscription with both success and failure. In case you need success model can replace Completable with Single or Flowable.
fetchFitnessData.subscrible(Functions.EMPTY_ACTION, Timber::d);
The major advantage with this approach is that you handle your subscription lifecycles.

android livedata make sequential call

I am using Retrofit, Live data. There is one situation on my project, I have to make sequence of network call. if any one fails it should return error.
At present I have two live data observers to get the work done, which is not good approach so I wanted to know the better approach or sample code to handle such requirement.
Note: I am not using Rxjava.
View code Basic logic
String id = "items/1233"; //ID which has to to be deleted
if (isCustomizedItem) {
viewModel.deleteEvent(id);
} else {
viewModel.createCustomItems();
viewModel.deleteEvent(id);
}
Livedata observers
viewModel.getItemDeleted().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("Successfully deleted");
}
});
viewModel.itemCreated().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("new items added");
//Again call delete for specific item
viewModel.deleteEvent(id);
}
});
Viewmodel code
createItems = Transformations.switchMap(eventData, (data) -> {
if (canCreateItems(data)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.createItems();
}
});
deleteItem = Transformations.switchMap(deleteItem, (item) -> {
if (!isValidItem(item)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.deleteItem(item);
}
});
Repo code.
public LiveData<Resource<List<Items>>> createItems() {
return new NetworkBoundResource<List<Items>> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<List<Items>>> createCall() {
return services.createItems();
}
}.asLiveData();
}
public LiveData<Resource<EmptyResponse>> deleteItem(String id) {
return new NetworkBoundResource<EmptyResponse> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<EmptyResponse>> createCall() {
return services.deleteItem(id);
}
}.asLiveData();
}
Service interface.
#GET(Constants.API_PATH+"/createitems/")
LiveData<ApiResponse<List<Items>>> createItems();
#GET(Constants.API_PATH+"/delete/{id}")
LiveData<ApiResponse<EmptyResponse>> deleteItem(#Path("id") String id);
I want to call createItems and deleteItem together. How can i achieve this?
Finally I write the solution. I used Mediatorlivedata to observe livedata changes on viewmodel.
Method which is responsible for both network call
public LiveData<Resource<EmptyResponse>> updateEvent(RequestCustomEvent request) {
return new UpdateItineraryRequests<EmptyResponse>(request).asLiveData();
}
and a class which will observe live data changes on viewmodel.
private class UpdateItineraryRequests<RequestType> {
private final MediatorLiveData<Resource<RequestType>> result = new MediatorLiveData<>();
UpdateItineraryRequests(RequestCustomEvent request) {
startExecution(request);
}
void startExecution(RequestCustomEvent request) {
//First check the its custom or not if its custom then directly change.
if (request.isCustom()) {
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
result.setValue(Resource.success(null));
} else {
result.setValue(Resource.error("unable to delete", null));
}
});
} else {
LiveData<Resource<List<Items>>> itemsObservable = repo.createItems(request.getDataToChange());
result.addSource(itemsObservable, response -> {
result.removeSource(itemsObservable);
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
//Do rest of network calls
}
}
});
}
}
LiveData<Resource<RequestType>> asLiveData() {
return result;
}
}

Categories

Resources