I am new in RX Java and I have a issue.I have a completable and want to continue my operations after I got a value and did some actions in one of the steps of emmition.
if (mIsCopy) {
completable = AppManagers.getContentManager().completeCopy(mContent).toCompletable().andThen(completable).doOnComplete(() -> {
createThumbnailsForNewContent(mContent); //I want this to be happen before completable.cache();
});
}
completable = completable.cache();
completable.subscribe(new SimpleCompletableSubscriber() {
#Override
public void onComplete() {
TypeUtil.empty(mNewTags);
mOriginalSubmitToUsers.clear();
mOriginalSubmitToUsers.addAll(mSubmitToUsers);
mOriginalSubmitToGroups.clear();
mOriginalSubmitToGroups.addAll(mSubmitToGroups);
mOriginalSubmitToChannels.clear();
mOriginalSubmitToChannels.addAll(mSubmitToChannels);
mOriginalSubmitToRelationships.clear();
mOriginalSubmitToRelationships.addAll(mSubmitToRelationships);
mIsCopy = false;
}
createThumbnailsForNewContent(mContent); returns completable,thx in advance.
public Single<Long> completeCopy(final Content copiedContent) {
return Single.fromCallable(new Callable<Long>() {
#Override
public Long call() throws Exception {
if (!copiedContent.isLocalCopy() || copiedContent.getId() != null) {
throw new IllegalArgumentException("Content passed must be an unsaved local copy.");
}
if (getDBManager().insertOrUpdateContent(copiedContent)) {
if (copiedContent.getCopiedFromCloudID() != null && copiedContent.getCopiedFromCloudID() > 0) {
AppManagers.getAppContext()
.getRequestQueue()
.forRequest(new InitiateCopyRequest(AppUserData.shared.getAccessKey(),
copiedContent.getCopiedFromCloudID()))
.withPriority(RequestQueue.Priority.UPDATE)
.attachLocalID(copiedContent.getId())
.enqueue();
}
} else {
throw new ErrorCodeException(ErrorCodes.UPDATE_CONTENT_FAILED);
}
return copiedContent.getId();
}
}).subscribeOn(getIOScheduler());
}
Don't really get the problem, but according to your code it seems that the method createThumbnailsForNewContent is never executed. That's because doOnComplete accepts as param an Action (this is a Runnable but allows throwing checked exceptions).
If you want to execute createThumbnailsForNewContent once the previous Completable is completed, you have to use the operator andThen(CompletableSource next).
Related
I am new to Rxjava. It is so complex......
I need to handle a complex scenario, I want to know how to make it using Rxjava.
It's a logic about app start:
app start and splash page open
make http reqesut check_update to check if app need update.
if app has new version and must update, pop up a notice dialog with only one update button; if app has new version, but not must update, then pop up a dialog with 2 button(update and ignore); if no new version, go next.
no need to update, so we need to jump to home page or login page, according to current auth_token state.
read auth_token from sharedPreference. then call http request check_auth to check if it is valid or not. if valid, jump to home page; if not, jump to login page.
In this process, 2 http requests and lots of condition checks are involved. http request error also need to be handled, Can this process be written in one Rxjava?
I think I can do it in this way, but I am not sure if it is right.
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Am I right? And would you please answer my questions(comments in the code)?
According to the documentation onError "Notifies the Observer that the Observable has experienced an error condition.". So this callback is not relevant to handle request errors.
I suggest you to use the Response class of retrofit to handle your differents cases.
For example you define your service like that :
#method(url)
Call<CheckAuthResponse> checkUpdate(...);
and in your rx workflow :
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.map((Function<Response<CheckUpdateResponse>, CheckUpdateResponse>) retrofitResponse -> {
if ( retrofitResponce.code() == authInvalidCode ) {
//do something
} else if (...) {
}
return retrofitResponse.body()
})
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Hope this helps;
Sorry for my english.
I'm fairly new to RxJava and RxAndroid, and while some things work, I'm now completely stumped by what I see as basic functionality not working.
I have a subscribe call on a Subject that never seems to run, and I can't figure out why:
public class PairManager implements DiscoveryManagerListener {
private Subscription wifiAvailableSubscription;
private Subscription debugSubscription;
private DiscoveryManager discoveryManager;
private AsyncSubject<Map<String, ConnectableDevice>> availableDevices;
public PairManager(Context appContext) {
DiscoveryManager.init(appContext);
discoveryManager = DiscoveryManager.getInstance();
discoveryManager.addListener(this);
availableDevices = AsyncSubject.<Map<String, ConnectableDevice>> create();
//
// This subscription doesn't work
//
debugSubscription = availableDevices
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Map<String, ConnectableDevice>>() {
#Override
public void call(Map<String, ConnectableDevice> stringConnectableDeviceMap) {
//
// This code is never run !
//
Timber.d(">> Available devices changed %s", stringConnectableDeviceMap);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Timber.d("Subscription failed %s", throwable);
}
});
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
wifiAvailableSubscription = ReactiveNetwork.observeNetworkConnectivity(appContext)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Connectivity>() {
#Override
public void call(Connectivity connectivity) {
if (connectivity.getState().equals(NetworkInfo.State.CONNECTED) && connectivity.getType() == ConnectivityManager.TYPE_WIFI) {
discoveryManager.start();
} else {
discoveryManager.stop();
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
}
}
});
}
public AsyncSubject<Map<String, ConnectableDevice>> getAvailableDevices() {
return availableDevices;
}
#Override
public void onDeviceAdded(DiscoveryManager manager, ConnectableDevice device) {
Timber.d("onDeviceAdded %s", device);
availableDevices.onNext(manager.getAllDevices());
Timber.d("Sanity check %s", availableDevices.getValue());
}
// ...
}
Is there a way to debug what is going wrong? I have tried creating basic Observable.from-type calls and logging those, and that works as expected. The sanity check log in onDeviceAdded also prints and indicates that availableDevices has in fact updated as expected. What am I doing wrong?
I've found the issue, I've used AsyncSubjects which only ever emit values when they are Completed, where I expect the functionality of BehaviorSubjects.
From the doccumentation:
When Connectivity changes, subscriber will be notified. Connectivity can change its state or type.
You say:
I have a subscribe call on a Subject
A subject won't return te last value. I will only return a value when onNext is called. I assume the Connectivity never changes so it never fires.
I have a list of objects that I want retrieve from a local database (if available), or from a remote server otherwise. I'm using RxJava Observables (SqlBrite for the database and Retrofit for the remote server).
My query code is as follows:
Observable<List<MyObject>> dbObservable = mDatabase
.createQuery(MyObject.TABLE_NAME,MyObject.SELECT_TYPE_A)
.mapToList(MyObject.LOCAL_MAPPER);
Observable<List<MyObject>> remoteObservable = mRetrofitService.getMyObjectApiService().getMyObjects();
return Observable.concat(dbObservable, remoteObservable)
.first(new Func1<List<MyObject>, Boolean>() {
#Override
public Boolean call(List<MyObject> myObjects) {
return !myObjects.isEmpty();
}
});
I see the first observable running and hitting the first method with an empty list, but then the retrofit observable does not run, there is no network request. If I switch the order of the observables, or just return the remote observable, it works as expected, it hits the remote server and returns the list of objects.
Why would the remote observable fail to run in this scenario? The subscriber's onNext, orError and onComplete methods are not called when I concatenate the observables with the db first and retrofit second.
Thanks!
Kaushik Gopal has addressed this in his RxJava-Android-Samples github project.
He recommends using this technique:
getFreshNetworkData()
.publish(network ->
Observable.merge(network,
getCachedDiskData().takeUntil(network)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<MyObject>() {
...
});
In your case, it might look like this:
remoteObservable
.publish(network ->
Observable.merge(network,
dbObservable.takeUntil(network)))
.first(myObjects -> !myObjects.isEmpty());
Edit: It sounds like you just might need this:
dbObservable
.flatMap(localResult -> {
if (localResult.isEmpty()) {
return remoteObservable;
} else {
return Observable.just(localResult);
}
});
I assume you have your observables which can get data from your local and remote like below:
final Observable<Page> localResult = mSearchLocalDataSource.search(query);
final Observable<Page> remoteResult = mSearchRemoteDataSource.search(query)
.doOnNext(new Action1<Page>() {
#Override
public void call(Page page) {
if (page != null) {
mSearchLocalDataSource.save(query, page);
mResultCache.put(query, page);
}
}
});
Then you can map them and get first which means if local available use local if not use remote:
return Observable.concat(localResult, remoteResult)
.first()
.map(new Func1<Page, Page>() {
#Override
public Page call(Page page) {
if (page == null) {
throw new NoSuchElementException("No result found!");
}
return page;
}
});
And subscribe it like below:
mCompositeSubscription.clear();
final Subscription subscription = mSearchRepository.search(this.mQuery)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Page>() {
#Override
public void onCompleted() {
// Completed
}
#Override
public void onError(Throwable e) {
mView.onDefaultMessage(e.getMessage());
}
#Override
public void onNext(Page page) {
mView.onDefaultMessage(page.getContent());
}
});
mCompositeSubscription.add(subscription);
For more detail or example you can check my github repo:
https://github.com/savepopulation/wikilight
Good luck!
Edit:
You can try a local observable like below. Simply it checks if there's a record and returns an empty observable.
#Override
public Observable<Page> search(#NonNull final String query) {
return Observable.create(new Observable.OnSubscribe<Page>() {
#Override
public void call(Subscriber<? super Page> subscriber) {
final Realm realm = Realm.getInstance(mRealmConfiguration);
final Page page = realm.where(Page.class)
.equalTo("query", query)
.findFirst();
if (page != null && page.isLoaded() && page.isValid()) {
Log.i("data from", "realm");
subscriber.onNext(realm.copyFromRealm(page));
} else {
Observable.empty();
}
subscriber.onCompleted();
realm.close();
}
});
}
Edit 2:
When you return null from local concat and first will not work and your remote will not be called because null means observable returns null but still can observe. When you return observable.empty with concat and first this means observable cannot emit anything from local more and so it can emit from remote.
I'm trying to implement a provider that looks for items in memory, disk, network, in this order. The main purpose of this is to avoid network calls if I have the right local cache. There is a catch, since my calls to the network use filters to get items, I could have 10 items from the local query, but still need to go to the network because those items come from a different network call, with different query parameters.
Right now I'm using a concat with a firstOrDefault, checking that the list isn't null or empty. I've implemented a way to check if I already called the server with the specific query and I use it to return null when reading from disk.
I now need to refine the provider, so that it:
emits local items
go online if needed
emits online items
(Right now it stops at the first good list of items).
I'm trying with takeWhile, using a method that returns true if data is null or empty or if I didn't already call the server for that query. The problem is that takeWhile doesn't emit the item if the check for that item is false, meaning I won't get the last good item (which is also the best one).
The best solution I can think of is an operator that emits items until a certain condition comes up and then unsubscribe itself. I can't find one.
EDIT: SOME CODE
Solution 1) using firstOrDefault: will not emit local items if !DiskService.wasDownloaded(), because DiskService return a null List<Item> with !DiskService.wasDownloaded()
public Observable<List<Item>> items() {
List<Observable> obs = new ArrayList<>();
Observable<List<Item>> memoryObs = Observable.defer(this::getMemoryItems);
Observable<List<Item>> diskObs = Observable.defer(this::getDiskItems);
Observable<List<Item>> networkObs = Observable.defer(this::getNetworkItems);
Observable<List<Item>> concat = Observable.concat(memoryObs, diskObs, networkObs;
return concat.firstOrDefault(new ArrayList<>(), this::canAccept);
}
private boolean canAccept(List<Item> data) {
return data != null && data.size() > 0;
}
//Method in DiskService
public boolean wasDownloaded(){
return true if the query was performed on the server, false otherwise.
}
Solution 2) Using takeWhile. The problem with takeWhile is the Observable will not emit the item that doesn't check its condition, meaning I won't get the best List. The hacky solution is to defer the false check to the next item, but this way a network request will be fired even when not necessary. With this solution I'm using a TrustedItemList that just contains the List and a boolean that tells the Observable if he can trust a non-empty list of items (always true for memory and network, true if wasDownloaded() for disk)
public Observable<List<Item>> items() {
List<Observable> obs = new ArrayList<>();
Observable<TrustedItemList> memoryObs = Observable.defer(this::getMemoryItems);
Observable<TrustedItemList> diskObs = Observable.defer(this::getDiskItems);
Observable<TrustedItemList> networkObs = Observable.defer(this::getNetworkItems);
Observable<TrustedItemList> concat = Observable.concat(memoryObs, diskObs, networkObs;
return concat.takeWhile(this::shouldContinueSearching)
.filter(trustedItemList -> trustedItemList.items != null && !trustedItemList.items.isEmpty())
.map(trustedItemList -> trustedItemList.items);
}
private boolean shouldContinueSearching(TrustedPoiList data) {
return data == null || data.items == null || data.items.isEmpty() || !data.canTrustIfNotEmpty;
}
I ended up using a custom Observable.Operator, shamelessly copied from OperatorTakeWhile, with the sole change of calling subscriber.onNext(t) just before subscriber.onCompleted() in the onNext method. This way the last item, the one that returns false on the boolean check, is emitted.
public final class OperatorTakeWhileWithLast<T> implements Observable.Operator<T, T> {
private final Func2<? super T, ? super Integer, Boolean> predicate;
public OperatorTakeWhileWithLast(final Func1<? super T, Boolean> underlying) {
this((input, index) -> {
return underlying.call(input);
});
}
public OperatorTakeWhileWithLast(Func2<? super T, ? super Integer, Boolean> predicate) {
this.predicate = predicate;
}
#Override
public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
Subscriber<T> s = new Subscriber<T>(subscriber, false) {
private int counter = 0;
private boolean done = false;
#Override
public void onNext(T t) {
boolean isSelected;
try {
isSelected = predicate.call(t, counter++);
} catch (Throwable e) {
done = true;
Exceptions.throwIfFatal(e);
subscriber.onError(OnErrorThrowable.addValueAsLastCause(e, t));
unsubscribe();
return;
}
if (isSelected) {
subscriber.onNext(t);
} else {
done = true;
subscriber.onNext(t); //Just added this line
subscriber.onCompleted();
unsubscribe();
}
}
#Override
public void onCompleted() {
if (!done) {
subscriber.onCompleted();
}
}
#Override
public void onError(Throwable e) {
if (!done) {
subscriber.onError(e);
}
}
};
subscriber.add(s);
return s;
}
}
My items() method (solution 2) now ends with:
return concat.lift(new OperatorTakeWhileWithLast<TrustedItemList>(this::shouldContinueSearching))
.filter(trustedItemList -> trustedItemList.items != null && !trustedItemList.items.isEmpty())
.map(trustedItemList -> trustedItemList.items);
I'm trying to wrap my head around RxJava currently, but I'm having a little trouble with handling service call exceptions in an elegant manner.
Basically, I have a (Retrofit) service that returns an Observable<ServiceResponse>. ServiceResponse is defined like so:
public class ServiceResponse {
private int status;
private String message;
private JsonElement data;
public JsonElement getData() {
return data;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
Now what I want is to map that generic response to a List<Account> contained within the data JsonElement field (I assume you don't care what the Account object looks like, so I won't pollute the post with it). The following code works really well for the success case, but I can't find a nice way to handle my API exceptions:
service.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<ServiceResponse, AccountData>() {
#Override
public AccountData call(ServiceResponse serviceResponse) {
// TODO: ick. fix this. there must be a better way...
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
switch (responseType) {
case SUCCESS:
Gson gson = new GsonBuilder().create();
return gson.fromJson(serviceResponse.getData(), AccountData.class);
case HOST_UNAVAILABLE:
throw new HostUnavailableException(serviceResponse.getMessage());
case SUSPENDED_USER:
throw new SuspendedUserException(serviceResponse.getMessage());
case SYSTEM_ERROR:
case UNKNOWN:
default:
throw new SystemErrorException(serviceResponse.getMessage());
}
}
})
.map(new Func1<AccountData, List<Account>>() {
#Override
public List<Account> call(AccountData accountData) {
Gson gson = new GsonBuilder().create();
List<Account> res = new ArrayList<Account>();
for (JsonElement account : accountData.getAccounts()) {
res.add(gson.fromJson(account, Account.class));
}
return res;
}
})
.subscribe(accountsRequest);
Is there a better way to do this? This does work, onError will fire to my observer, and I will receive the error that I threw, but it definitely does not seem like I'm doing this right.
Thanks in advance!
Edit:
Let me clarify exactly what I want to achieve:
I want to have a class that can be called from the UI (e.g. an Activity, or Fragment, or whatever). That class would take an Observer<List<Account>> as a parameter like so:
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
...
}
That method would return a subscription that can be unsubscribed when the UI is detached/destroyed/etc.
The parameterized observer would handle onNext for the successful responses passing in a list of Accounts. OnError would handle any exceptions, but would also get passed any API exceptions (e.g. if the response status != 200 we would create a Throwable and pass it to onError). Ideally I don't want to just "throw" the Exception, I want to pass it directly to the Observer. That's what all the examples I see do.
The complication is that my Retrofit service returns a ServiceResponse object, so my observer cannot subscribe to that. The best I've come up with is to create an Observer wrapper around my Observer, like so:
#Singleton
public class AccountsDatabase {
private AccountsService service;
private List<Account> accountsCache = null;
private PublishSubject<ServiceResponse> accountsRequest = null;
#Inject
public AccountsDatabase(AccountsService service) {
this.service = service;
}
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
ObserverWrapper observerWrapper = new ObserverWrapper(observer);
if (accountsCache != null) {
// We have a cached value. Emit it immediately.
observer.onNext(accountsCache);
}
if (accountsRequest != null) {
// There's an in-flight network request for this section already. Join it.
return accountsRequest.subscribe(observerWrapper);
}
if (accountsCache != null && !forceRefresh) {
// We had a cached value and don't want to force a refresh on the data. Just
// return an empty subscription
observer.onCompleted();
return Subscriptions.empty();
}
accountsRequest = PublishSubject.create();
accountsRequest.subscribe(new ObserverWrapper(new EndObserver<List<Account>>() {
#Override
public void onNext(List<Account> accounts) {
accountsCache = accounts;
}
#Override
public void onEnd() {
accountsRequest = null;
}
}));
Subscription subscription = accountsRequest.subscribe(observerWrapper);
service.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
return subscription;
}
static class ObserverWrapper implements Observer<ServiceResponse> {
private Observer<List<Account>> observer;
public ObserverWrapper(Observer<List<Account>> observer) {
this.observer = observer;
}
#Override
public void onCompleted() {
observer.onCompleted();
}
#Override
public void onError(Throwable e) {
observer.onError(e);
}
#Override
public void onNext(ServiceResponse serviceResponse) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
switch (responseType) {
case SUCCESS:
Gson gson = new GsonBuilder().create();
AccountData accountData = gson.fromJson(serviceResponse.getData(), AccountData.class);
List<Account> res = new ArrayList<>();
for (JsonElement account : accountData.getAccounts()) {
res.add(gson.fromJson(account, Account.class));
}
observer.onNext(res);
observer.onCompleted();
break;
default:
observer.onError(new ApiException(serviceResponse.getMessage(), responseType));
break;
}
}
}
}
I still feel like I am not using this correctly though. I definitely haven't seen anyone else using an ObserverWrapper before. Perhaps I shouldn't be using RxJava, though the guys at SoundCloud and Netflix really sold me on it in their presentations and I'm pretty eager to learn it.
Please read below I've added an edit.
It's perfectly correct to throw within an Action/Func/Observer with RxJava. The exception will be propagate by the framework right down to your Observer.
If you limit yourself to calling onError only then you'll be twisting yourself to make that happen.
With that being said a suggestion would be to simply remove this wrapper and add a simple validation
Action within the service.getAccount... chain of Observables.
I'd use the doOnNext(new ValidateServiceResponseOrThrow) chained with a map(new MapValidResponseToAccountList). Those are simple classes which implements the necessary code to keep the Observable chain a bit more readable.
Here's your loadAccount method simplified using what I suggested.
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
if (accountsCache != null) {
// We have a cached value. Emit it immediately.
observer.onNext(accountsCache);
}
if (accountsRequest != null) {
// There's an in-flight network request for this section already. Join it.
return accountsRequest.subscribe(observer);
}
if (accountsCache != null && !forceRefresh) {
// We had a cached value and don't want to force a refresh on the data. Just
// return an empty subscription
observer.onCompleted();
return Subscriptions.empty();
}
accountsRequest = PublishSubject.create();
accountsRequest.subscribe(new EndObserver<List<Account>>() {
#Override
public void onNext(List<Account> accounts) {
accountsCache = accounts;
}
#Override
public void onEnd() {
accountsRequest = null;
}
});
Subscription subscription = accountsRequest.subscribe(observer);
service.getAccounts()
.doOnNext(new ValidateServiceResponseOrThrow())
.map(new MapValidResponseToAccountList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
return subscription;
}
private static class ValidateResponseOrThrow implements Action1<ServiceResponse> {
#Override
public void call(ServiceResponse response) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
if (responseType != SUCCESS)
throw new ApiException(serviceResponse.getMessage(), responseType));
}
}
private static class MapValidResponseToAccountList implements Func1<ServiceResponse, List<Account>> {
#Override
public Message call(ServiceResponse response) {
// add code here to map the ServiceResponse into the List<Accounts> as you've provided already
}
}
Edit:
Unless someone says otherwise I think it's best practice to return errors using flatMap.
I've thrown Exceptions from Action in the past but I don't believe it's the recommended way.
You'll have a cleaner Exception stack if you use flatMap. If you throw from inside an Action the Exception stack
will actually contain rx.exceptions.OnErrorThrowable$OnNextValue Exception which isn't ideal.
Let me demonstrate the example above using the flatMap instead.
private static class ValidateServiceResponse implements rx.functions.Func1<ServiceResponse, Observable<ServiceResponse>> {
#Override
public Observable<ServiceResponse> call(ServiceResponse response) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
if (responseType != SUCCESS)
return Observable.error(new ApiException(serviceResponse.getMessage(), responseType));
return Observable.just(response);
}
}
service.getAccounts()
.flatMap(new ValidateServiceResponse())
.map(new MapValidResponseToAccountList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
As you can see the the difference is subtle. The ValidateServiceResponse now implements the Func1 instead of Action1 and we're no longer using the throw keyword. We use Observable.error(new Throwable) instead. I believe this fits better with the expected Rx contract.
You could read this good article about error handling http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/