I want to be able to have Espresso monitor Picasso as an IdlingResource so that I can run ViewMatchers once the image has been successfully loaded.
From navigating through the Picasso source code, I don't see why this isn't working. Here's what I tried:
Picasso picasso = new Picasso.Builder(context).build();
Field dispatcherField = Picasso.class.getDeclaredField("dispatcher");
dispatcherField.setAccessible(true);
try {
Dispatcher dispatcher = (Dispatcher) dispatcherField.get(picasso);
Espresso.registerLooperAsIdlingResource(dispatcher.dispatcherThread.getLooper());
} catch (NoSuchFieldException e) {
throw new PicassoHasBeenRefactoredException();
} catch (Exception e) {
e.printStackTrace();
}
onView(withId(R.id.image_view)).check(matches(withImage(R.drawable.drawable)));
(yes, I know, reflecting is icky, but I couldn't find another way of getting a handle on the Looper)
But it results in this error when trying to get the Bitmap from the ImageView:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.graphics.Bitmap android.graphics.drawable.BitmapDrawable.getBitmap()' on a null object reference
To check that the test is running as expected once an image has been loaded, I tried introducing a Thread.sleep(1000) in lieu of the IdlingResource check and it passed.
Is it safe to assume that the IdlingResource hasn't been set up correctly, and, more importantly, what would be the correct way of waiting for Picasso to finish loading before checking views with Espresso?
I'm using an IdlingResource that checks if there are actions left.
Note that the IdlingResource must live in the same package as Picasso to gain access to a package-protected variable
package com.squareup.picasso;
public class PicassoIdlingResource implements IdlingResource, ActivityLifecycleCallback {
protected ResourceCallback callback;
WeakReference<Picasso> picassoWeakReference;
#Override
public String getName() {
return "PicassoIdlingResource";
}
#Override
public boolean isIdleNow() {
if (isIdle()) {
notifyDone();
return true;
} else {
return false;
}
}
public boolean isIdle() {
return picassoWeakReference == null
|| picassoWeakReference.get() == null
|| picassoWeakReference.get().targetToAction.isEmpty();
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.callback = resourceCallback;
}
void notifyDone() {
if (callback != null) {
callback.onTransitionToIdle();
}
}
#Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
switch (stage) {
case CREATED:
picassoWeakReference = new WeakReference<>(Picasso.with(activity));
break;
case STOPPED:
// Clean up reference
picassoWeakReference = null;
break;
default: // NOP
}
}
}
I don't think using WeakReference is needed, but it doesn't hurt either.
Also, I've identified one case where it doesn't wait until Picasso finishes (when using .load(null)). So, use at your own risk and please come back if you improve it.
See gist for full details and usage (https://gist.github.com/Maragues/0c0db81a137c8d067396)
Related
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).
I'm trying to set wallpaper by downloading an image from internet but it's showing that "get()method" is not found.
My Code:
In this code wall_set is a button's name
wall_set.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
Bitmap result=Glide.with(getApplicationContext())
.load("http://www.sport-stickers.com/images/2013/CARTOON%20IRON%20ONS/Doraemon/Doraemon%20Iron%20ons%20(Wall%20Stickers)%20N3715.jpg").get();
WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());
try {
wallpaperManager.setBitmap(result);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
);
change this part of your code:
Bitmap result=Glide.with(getApplicationContext())
.load("http://www.sport-stickers.com/images/2013/CARTOON%20IRON%20ONS/Doraemon/Doraemon%20Iron%20ons%20(Wall%20Stickers)%20N3715.jpg").asBitmap().get();
add "asBitmap"
you might need to add
asBitmap().into(20, 20). // Width and height
in the following
If you follow Glide sample usage it comes up with that get() method belongs to java.util.concurrent.Future object. And Future class definition is given by the official doc as below.
public interface Future<V> A Future represents the result of an
asynchronous computation. Methods are provided to check if the
computation is complete, to wait for its completion, and to retrieve
the result of the computation. The result can only be retrieved using
method get when the computation has completed, blocking if necessary
until it is ready. Cancellation is performed by the cancel method.
Additional methods are provided to determine if the task completed
normally or was cancelled. Once a computation has completed, the
computation cannot be cancelled. If you would like to use a Future for
the sake of cancellability but not provide a usable result, you can
declare types of the form Future and return null as a result of the
underlying task.
Sample Usage (Note that the following classes are all made-up.)
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target)
throws InterruptedException {
Future<String> future
= executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
Lets see what happens step by step:
Bitmap theBitmap = Glide.
with(this). //in Glide class and returns RequestManager
load(image_url). // in RequestManager and returns RequestBuilder<Drawable>
asBitmap(). //in RequestBuilder and returns RequestBuilder<Bitmap>
submit(). // in RequestBuilder and returns FutureTarget<TranscodeType> which extends Future<>
get(); // this belongs to Future object which is the result of async computation
public static RequestManager with(Context context) {
return getRetriever(context).get(context);
}
public RequestBuilder<Drawable> load(#Nullable Object model) {
return asDrawable().load(model);
}
public RequestBuilder<Bitmap> asBitmap() {
return as(Bitmap.class).transition(new GenericTransitionOptions<Bitmap>())
.apply(DECODE_TYPE_BITMAP);
}
public FutureTarget<TranscodeType> submit() {
return submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
public interface FutureTarget<R> extends Future<R>, Target<R> {
}
But more proper and secure solution is by using callback
Glide
.with(this)
.load(image_url)
.asBitmap()
.into(new SimpleTarget<Bitmap>(100,100) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
//resource is the resulting bitmap
}
});
I just start learning rxJava for Android and want to implement the common use case:
request data from cache and show to the user
request data from web
server update data in storage and automatically show it to the user
Traditionally on of the best scenarios was use CursorLoader to get data from cache, run web request in the separate thread and save data to the disk via content provider, content provider automatically notify the listener and CursorLoader autoupdate UI.
In rxJava I can do it by running two different Observers as you can see in code below, but I don't find the way how to combine this two calls into the one to reach my aim. Googling shows this thread but it looks like it just get data from the cache or data from the web server, but don't do both RxJava and Cached Data
Code snippet:
#Override
public Observable<SavingsGoals> getCachedSavingsGoal() {
return observableGoal.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
#Override
public Observable<SavingsGoals> getRecentSavingsGoal() {
return api.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
model.getCachedSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume cached data");
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
Log.d(App.TAG, "Show the next item");
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
model.getRecentSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume data from the web", e);
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
Also, the one of issues with current approach is cache and web data are not garranted to be run sequently. It is possible when outdated data will come as latest and override recent from web.
To solve this issue I implemented Observer merge with filtration by timestamp: it get data from cache, pass it to the next observer and if cache is outdated fire new call to the web - case for thread competition solved by the filtration with timestamps. However, the issue with this approach I can not return cache data from this Observable - I need to wait when both requests finish their work.
Code snippet.
#Override
public Observable<Timestamped<SavingsGoals>> getSavingGoals() {
return observableGoal
.getTimestampedSavingsGoals()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> cachedData) {
Log.d(App.FLOW, "getTimestampedSavingsGoals");
return getGoalsFromBothSources()
.filter(filterResponse(cachedData));
}
})
.subscribeOn(AndroidSchedulers.mainThread());
}
private Func1<Timestamped<SavingsGoals>, Boolean> filterResponse(Timestamped<SavingsGoals> cachedData) {
return new Func1<Timestamped<SavingsGoals>, Boolean>() {
#Override
public Boolean call(Timestamped<SavingsGoals> savingsGoals) {
return savingsGoals != null
&& cachedData != null
&& cachedData.getTimestampMillis() < savingsGoals.getTimestampMillis()
&& savingsGoals.getValue().getSavingsGoals().size() != 0;
}
};
}
private Observable<Timestamped<SavingsGoals>> getGoalsFromBothSources() {
Log.d(App.FLOW, "getGoalsFromBothSources:explicit");
return Observable.merge(
observableGoal.getTimestampedSavingsGoals().subscribeOn(Schedulers.io()),
api.getSavingsGoals()
.timestamp()
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> savingsGoals) {
Log.d(App.FLOW, "getGoalsFromBothSources:implicit");
return observableGoal.saveAllWithTimestamp(savingsGoals.getTimestampMillis(), savingsGoals.getValue().getSavingsGoals());
}
}))
.subscribeOn(Schedulers.io());
}
Do you know the approach to do this in one Observer?
Potential solution:
#Override
public Observable<SavingsGoals> getSavingGoals() {
return api.getSavingsGoals()
.publish(network ->
Observable.mergeDelayError(
observableGoal.getSavingsGoals().takeUntil(network),
network.flatMap(new Func1<SavingsGoals, Observable<SavingsGoals>>() {
#Override
public Observable<SavingsGoals> call(SavingsGoals savingsGoals) {
return observableGoal.saveAll(savingsGoals.getSavingsGoals());
}
})
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
Sorry, hot replacement in IDE hide the issue which this approach has: first one in case if network unavailable and cache thread completes first, the error will terminate whole merge (solved by mergeDelayError), second one is in case when cache is empty and returns first data from web request will not be return on subscriber. As you can see my method returns Observable after save and traditional merge as I shown in my code properly handle this case but takeUntil by some reason can not. Question is still open.
For first question : You can save the result from Network Result by using doOnNext Method, It would looks something like this
public Observable<NetworkResponse> getDataFromNetwork(
final Request request) {
return networkCall.doOnNext(networkResponse -> saveToStorage(networkResponse);
}
Now to combine the two results from both Storage and Online, the best way is to combine with publish and merge. I recommend watching this talk.
The code would look something like this
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)));
}
Why use publish and merge you my ask? publish method makes the response accessible in the callback. takeUntil means that you will take the data from storage but you will stop it IF for some reason, network call is finished before accessing storage data is finished. This way, you can be sure that new data from network is always shown even if it's finished before getting old data from storage.
The last but not least, in your subscriber OnNext just add the items to the list. (list.clear and list.addAll) Or similar functions or in you case view.showData()
EDIT: For The call getting disrupted when there's an error from network, add onErrorResumeNext at the end.
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)))
.onErrorResumeNext(dataService.getDataFromStorage(request);
}
I'd recommend to "listen" only to local data, and refresh it when API response came.
Let say for getting local data you have something like:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
});
}
So you need to add PublishSubject that will emit every time, when local data was updated (refreshSubject):
#Nonnull
public Observable<SomeData> getSomeDataObservableRefreshable() {
return refreshSubject.startWith((Object)null).switchMap(new Func1() {
public Observable<T> call(Object o) {
return getSomeDataObservable();
}
}
}
Now you need to subscribe only to getSomeDataObservableRefreshable(), and each time when data came from API, you update it and make refreshSubject .onNext(new Object())
Also i'd recommend to take a look to rx-java-extensions lib, it has alot of "cool tools" for RxAndroid. For example solution for your problem would be:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
})
.compose(MoreOperators.<SomeData>refresh(refreshSubject));
}
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/
I'm using 'Retrofit' for making asynchronous network requests, how might i right a function for handling logins? For instance i've currently attempted:
public UserAuthResponse Login(String username, String password) {
try {
Callback<UserAuthResponse> getAuthCallback = new Callback<UserAuthResponse>() {
#Override
public void failure(RetrofitError arg0) {
if (arg0 != null) {
if (arg0.getMessage() != null
&& arg0.getMessage().length() > 0) {
Log.e("KFF-Retrofit", arg0.getMessage());
}
}
}
#Override
public void success(UserAuthResponse listItem,
retrofit.client.Response arg1) {
Log.e("dg", listItem.getUser().getFirstname());
}
};
service.authUser(username, MD5(password), getAuthCallback);
return response;
} catch (RetrofitError e) {
e.printStackTrace();
return null;
}
}
But this is flawed: there is no way of returning the 'UserAuthResponse' from the function? How can i pass back the result?
It seems like i need a synchronous call to the web service but then i'm hit with a 'NetworkOnMainThreadException'
What is the best practice for things like this? Sorry about the poor explanation, struggling to form the right words.
Well the things is that when you're using the Callback as your means of getting the results from Retrofit you automatically giving away the possibility of having the response returned inline. There's a few ways this can be solved. I suppose it's up to you to choose which one fits best with your design.
You could decide to not use the Callback approach and use the inline result from Retrofit but then you'd need to handle the scheduling yourself otherwise you'll hit the Exception of NetworkOnMainThreadException like you mentioned.
You could also pass in a listener to your login method. This listener could then be called by the result Callback. This could be useful if you're trying to hide Retrofit behind some sort of service layer and expose a simple login interface.
interface OnLoginListener {
onLoginSuccessful(UserAuthResponse response);
onLoginFailed(Throwable t);
}
public void Login(String username, String password, final OnLoginListener listener) {
Callback<UserAuthResponse> getAuthCallback = new Callback<UserAuthResponse>() {
#Override
public void failure(RetrofitError e) {
// You can handle Retrofit exception or simply pass them down to the listener as is
listener.onLoginFailed(e);
}
#Override
public void success(UserAuthResponse listItem,
retrofit.client.Response arg1) {
// handle successful case here and pass down the data to the listener
listener.onLoginSucessful(listItem);
}
};
service.authUser(username, MD5(password), getAuthCallback);
}
use this line i Manifest
<uses-permission android:name="android.permission.INTERNET"/>
or use this before network operation (not suggestible)
if (Build.VERSION.SDK_INT>= 10) {
ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);
}