Making multiple asynchronous request using Retrofit - android

In my android application I have a screen where I have 3 spinners that need to be
filled from APIs call.
static List<TripCode> tripCodeList = new ArrayList<>();
static List<Fleet> truckList = new ArrayList<>();
static List<Trailer> trailerList = new ArrayList<>();
And I don't want to inflate the layout unless I get the response from all the 3 different API calls so this is what I'm doing
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
if (MyApplication.isConnected()) {
getTripCodes();
} else {
Toast.makeText(this, "No internet Connection", Toast.LENGTH_LONG).show();
setContentView(R.layout.no_internet_connection);
}
}
Basically , I removed setContentView(R.layout.activity_create_trip);
from onCreate() And I called getTripCodes()
here's the code for getTripCodes()
public void getTripCodes() {
MyApplication.showProgressDialog(getString(R.string.please_wait), this);
IMyAPI iMyAPI = MyApplication.getIMyAPI();
Call<List<TripCode>> call = iMyAPI.getTripCodes();
call.enqueue(new Callback<List<TripCode>>() {
#Override
public void onResponse(Call<List<TripCode>> call, Response<List<TripCode>> response) {
if (response.isSuccessful() && response.body() != null) {
tripCodeList = response.body();
Log.d("test", "getTripCodes success = " + tripCodeList.size());
getTrucks();
} else {
MyApplication.dismissProgressDialog();
}
}
#Override
public void onFailure(Call<List<TripCode>> call, Throwable t) {
MyApplication.dismissProgressDialog();
}
});
}
So in the success of the call I'm calling the other function getTrucks() which also get result from API and in the success it will call getTrailers()
But I think it's a waste of time, because I can call the three function all together in parallel, and then check if all the list are filled or not.
But I don't know how to do it. How can I check if all the calls are success? And if one of them has failed, how will I know which one exactly failed?

I Believe for your problem you can easily use Retrofit 2.6.0 which has coroutine support and you can declare all the function's as suspended function's and dispatch them with async/launch dispatcher and if you want to wait for some result in some case use await() to wait for the result.
And use RxJava/liveData for responsive UI
sample code for you will look like
//maybe from Activity for ViewModel you can use ViewModelScope
GlobalScope.launch{
result1= async{ getTripCodes() }
result2= async{ getTrucks() }
result3= async{ getTrailers() }
doSomethingWithTripCodes(result1.await())
doSomethingWIthTrucks(result2.await())
doSomethingTrailers(result3.await())
}
Reference:
post1

Related

Can we use Retrofit inside a loop in android studio?

I know this kind of a weird question but I am trying to use my Retrofit call inside a for loop. What I am doing is sending my String[] elements one by one in the call with func like insertdata(seperated2[0], seperated2[1], email, tag);
But the loop are behaving weirdly when they are skipping the anonymous call for call.enqueue(......onResponse(...) onfailure(.....))
Instead of calling it with the loop control first finishes the loop and then comes to call.enqueue and always last element in loop. This is how loop looks like ....
separated = currentString.split("\n");
for (int i=1; i<separated.length; i++) {
seperated2 = separated[i].split(":");
for (String aSeperated2 : seperated2) {
Call<ServerResponse2> call = requestInterface.insertQrdata(seperated2[0], seperated2[1], email, tag);
call.enqueue(new Callback<ServerResponse2>() {
#Override
public void onResponse(Call<ServerResponse2> call, Response<ServerResponse2> response) {
ServerResponse2 serverResponse2 = response.body();
Toast.makeText(getActivity(), serverResponse2 != null ? serverResponse2.getMessage() : null, Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<ServerResponse2> call, Throwable t) {
Toast.makeText(getActivity(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
here is an ex for seperated[] and seperated2[]
0 1
2 3
4 5
6 7
7 8
9 10
seperated[] is spliting them by line and seperated2 is spliting them by column.
The Problem
When I check my seperated2[0] and seperated2[1] value for each iteration in on Response method it should be
sep2[0]= 0 sep2[1] = 1
2 3
and so on... for each iteration
but in each iteration the value in onResponse is always the last, i.e.
sep2[0] = 9 sep2[1] = 10
untill the length (say 6) same value at each iteration.
I don't know if am doing anything wrong but values are showing correctly when i use them outside of onResponse().
I know using retrofit like is not good practice but I was curious to how it will react in this situation. Can anyone help or give any suggestions ?
THANKS IN ADVANCE !!
Here is an example of looping with retrofit library..if we use for loop the call.enqueue will not be called instantly for all iterations.so use this model.
private void UploadImagesTask(final String image, final int position) {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
File file = new File(image);
RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part photoArray = MultipartBody.Part.createFormData("photo", file.getName(), reqFile);
HashMap<String, RequestBody> params = new HashMap<>();
params.put("token", Utils.createPartFromString(pref.getToken()));
params.put("app_id", Utils.createPartFromString(pref.getAppId()));
Call<ImageUploadResponse> imageUploadResponseCall = apiService.uploadImage(photoArray, params);
imageUploadResponseCall.enqueue(new Callback<ImageUploadResponse>() {
#Override
public void onResponse(#NonNull Call<ImageUploadResponse> call, #NonNull Response<ImageUploadResponse> response) {
if (response.isSuccessful()) {
urlList.add(Objects.requireNonNull(response.body()).getUrl());
completeData.remove(position);
completeData.add(position, getString(R.string.uploaded));
uploadAdapter.notifyDataSetChanged();
pref.setTempData(Constants.IMAGE_UPLOADED, gson.toJson(urlList));
if (position != uriData.size() - 1) {
int posi = position + 1;
CompressImages(posi);
} else {
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
} else {
Utils.showSnackView(getString(R.string.error_occurred), snackView);
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
}
#Override
public void onFailure(#NonNull Call<ImageUploadResponse> call, #NonNull Throwable t) {
Utils.showSnackView(getString(R.string.error_occurred), snackView);
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
});
}
Instead of using for loop, you can use recursion too.
API will be called again and again but only after getting the response of the previous index.
In the onResponse() method you can call the method by incrementing the index value.
Because in for loop, the iteration will not wait for the completion of the execution of your API response, it will jump to the next iteration.
If you still want to use the loop then go for while loop
example for recursion:
int i=1;
separated = currentString.split("\n");
void callMethod(){
seperated2 = separated[i].split(":");
Call<ServerResponse2> call = requestInterface.insertQrdata(seperated2[0], seperated2[1], email, tag);
call.enqueue(new Callback<ServerResponse2>() {
#Override
public void onResponse(Call<ServerResponse2> call, Response<ServerResponse2> response) {
ServerResponse2 serverResponse2 = response.body();
Toast.makeText(getActivity(), serverResponse2 != null ? serverResponse2.getMessage() : null, Toast.LENGTH_SHORT).show();
i++;
callMethod();
}
#Override
public void onFailure(Call<ServerResponse2> call, Throwable t) {
Toast.makeText(getActivity(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
As per My suggestion do not use loops for this types of operation. Its bad idea If you can see performance wise.
You can do this things by following way:
Retrofit is just call server URL and passing your data to the server.
So whatever process doing server side that you can change to do once only rather than doing everytime.
You can pass whole loop data in json format to the server once and do all looping process in server side. It will be best for you.
Hope you clear my point of view.
Do let me know if you have any problem.
Hey if you are yet never found the answer I have a suggestion for you. Divide the whole response into parts after that apply loop on that data and send data using Asynctask to server and create one interface so you can do anything on a response back.
public interface GetResponse{
public void onSuccess(ResponseModel model);
publich void onFail();
}
In your async task
public class AsyncTaskForApiSync extends AsyncTask<Void, Void, Void> {
GetResponse mcallback;
public AsyncTaskForApiSync(GetResponse mcallBack){
this.mcallback=mcallBack;
}
#Override
protected Void doInBackground(Void... voids) {
//retrofit api call in on success
mcallback.onSuccess(response);
}
}
Sorry for my English. Also, let me know if you find any issue. Thanks.

Executing rx.Obseravables secuentially

I'm developing an Android App using Fernando Ceja's clean architecture. One of my Interactors or Use Cases is in charge of getting the User's feed data. In order to get the data, first I have to retrieve the User's Teams from a database table and then I have to get the Feed list from the server-side.
This is how I get the Teams from the database layer:
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
}
});
TeamCache is basically just another Interactor that takes care of getting all the teams that I have in the database.
Here's how I get the Feed data from the server-side:
mFeedRepository.getFeed(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed(...);
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
callback.onFeedFetched(...);
}
});
My GetFeedInteractor class has a method called execute, where I pass through the Callback that I'm later using in the UI to handle the response. The issue with all this is that currently I'm chaining the responses like this:
#Override
public void execute(final Callback callback, String userSipId) {
mTeamCache.getAllTeams().subscribe(new DefaultSubscriber<List<SimpleTeam>>() {
#Override
public void onNext(List<SimpleTeam> simpleTeams) {
super.onNext(simpleTeams);
mTeams = simpleTeams;
getFeedFromRepository(callback);
}
});
}
public void getFeedFromRepository(final Callback callback) {
mFeedRepository.getFeedRx(0, 50).subscribe(new ServerSubscriber<List<ApiFeedResponse>>() {
#Override
protected void onServerSideError(Throwable errorResponse) {
callback.onFeedFetchFailed("failed");
}
#Override
protected void onSuccess(List<ApiFeedResponse> responseBody) {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : responseBody) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
callback.onFeedFetched(responseList);
}
});
}
As you can see, once that I get the Team collection from the Cache Interactor I call the method that gets the feed from the very same Subscriber. I don't like this. I want to be able to do something nicer, like using Observable.concat(getTeamsFromCache(), getFeedFromRepository()); chain a call to another rx.Observable inside a Subscriber is not something nice to do. I guess that my question is, how can I chain two rx.Observables that are using different Subscribers?
Update:
ServerSubscriber is a subscriber that I implemted to subscribe to Retrofit services. It simply checks the error codes and some stuff. Here is:
https://gist.github.com/4gus71n/65dc94de4ca01fb221a079b68c0570b5
Default subscriber is an empty default subscriber. Here is:
https://gist.github.com/4gus71n/df501928fc5d24c2c6ed7740a6520330
TeamCache#getAllTeams() returns rx.Observable>
FeedRepository#getFeed(int page, int offset) returns rx.Observable>
Update 2:
This is how the Interactor to get the User's feed looks like now:
#Override
public void execute(final Callback callback, int offset, int pageSize) {
User user = mGetLoggedUser.get();
String userSipid = mUserSipid.get();
mFeedRepository.getFeed(offset, pageSize) //Get items from the server-side
.onErrorResumeNext(mFeedCache.getFeed(userSipid)) //If something goes wrong take it from cache
.mergeWith(mPendingPostCache.getAllPendingPostsAsFeedItems(user)) //Merge the response with the pending posts
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
callback.onFeedFetched(baseFeedItems);
}
#Override
public void onError(Throwable e) {
if (e instanceof ServerSideException) {
//Handle the http error
} else if (e instanceof DBException) {
//Handle the database cache error
} else {
//Handle generic error
}
}
});
}
I think you're missing the point of RxJava and reactive approach, you should not have different subscribers with OO hierarchy, and callbacks.
You should construct separated Observables that should emit the specific data it's handle, without the Subscriber, then you can chain you're Observable as needed, and at the end, you have the subscriber that react to the final result expected from the chained Observable stream.
something like this (using lambdas to have more thin code):
TeamCache mTeamCache = new TeamCache();
FeedRepository mFeedRepository = new FeedRepository();
Observable.zip(teamsObservable, feedObservable, Pair::new)
.subscribe(resultPair -> {
//Do stuff with mTeams
List<BaseFeedItem> responseList = new ArrayList();
for (ApiFeedResponse apiFeedResponse : resultPair.second) {
responseList.add(FeedDataMapper.transform(apiFeedResponse));
}
}, throwable -> {
//handle errors
}
);
I've use zip and not concat as it's seems you have 2 independent calls here that you want to wait for both to finish ('zip' them together) and then act upon, but ofcourse, as you have separated Observables stream, you can chain them together differently according to your needs.
as for your ServerSubscriber with all the response validation logic, it should be rxify too, so you can compose it along your server Observable stream.
something like this (some logic emitted to simplify, and as I'm not familiar with it...)
Observable<List<SimpleTeam>> teamsObservable = mTeamCache.getAllTeams();
Observable<List<ApiFeedResponse>> feedObservable = mFeedRepository.getFeed(0, 50)
.flatMap(apiFeedsResponse -> {
if (apiFeedsResponse.code() != 200) {
if (apiFeedsResponse.code() == 304) {
List<ApiFeedResponse> body = apiFeedsResponse.body();
return Observable.just(body);
//onNotModified(o.body());
} else {
return Observable.error(new ServerSideErrorException(apiFeedsResponse));
}
} else {
//onServerSideResponse(o.body());
return Observable.just(apiFeedsResponse.body());
}
});

Why async methods call sooner in Retrofit 2?

As you can see in the code below, I've call the getPostByPageId() method to get data from server and then check if the data was returned back, I do other jobs.
private void recyclerViewJobs() {
getPostByPageId();
if (pageDtoList.size() > 0) {
emptyText.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
PagesAdapter adapter = new PagesAdapter(pageDtoList, context);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
} else {
emptyText.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
}
.....
private void getPostByPageId() {
IPageEndPoint pageEndPoint = ServiceGenerator.createService(IPageEndPoint.class);
Call<List<PageDto>> call = pageEndPoint.getPostByPageId(profileId);
call.enqueue(new retrofit2.Callback<List<PageDto>>() {
#Override
public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {
if (response.isSuccessful()) {
pageDtoList = response.body();
} else {
log.toast("response is not successful for getPostByPageId");
}
}
#Override
public void onFailure(Call<List<PageDto>> call, Throwable t) {
log.toast("getPostByPageId onFailure");
}
});
}
I don't know why in the recyclerViewJobs() method, the if condition work first?
maybe I could not explain my issue very well but my big problem is to
know when I want to get some data from REST and then use this data to
another REST service, how can I do this job because enqueue in
retrofit works asynchronous and do not wait for first method, because
of that it is beginning the second method in another thread and
because my data was not gotten from first method yet, my program was
crashed and I didn't get the answer. That is my problem.....
Cheers
That's because retrofit's enqueue method is not a blocking method. which means the Thread calling it won't wait for it to finish its job.
Like #Amir said, If you have other things to do after calling getPostByPageId(), you should put them after retrofit's callback:
private void recyclerViewJobs() {
getPostByPageId();
}
private void getPostByPageId() {
IPageEndPoint pageEndPoint = ServiceGenerator.createService(IPageEndPoint.class);
Call<List<PageDto>> call = pageEndPoint.getPostByPageId(profileId);
call.enqueue(new retrofit2.Callback<List<PageDto>>() {
#Override
public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {
if (response.isSuccessful()) {
pageDtoList = response.body();
if (pageDtoList.size() > 0) {
emptyText.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
PagesAdapter adapter = new PagesAdapter(pageDtoList, context);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
} else {
emptyText.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
} else {
log.toast("response is not successful for getPostByPageId");
}
}
#Override
public void onFailure(Call<List<PageDto>> call, Throwable t) {
log.toast("getPostByPageId onFailure");
}
});
}
Your async call run first But your if-condition always goes false because if-condition not behave as you expected! As soon as your async request sent it goes to check if-condition and because your list is empty (your response from server is not fetch yet) it always return false.
If you want to call next operation just after your WebService response was successful you can do it with following code:
public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {
if (response.body().YOUR_LIST.size() > 0) {
emptyText.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
PagesAdapter adapter = new PagesAdapter(pageDtoList, context);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
//CALL_YOUR_NEXT webservice here
} else {
emptyText.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
}
In your processResponse do your job. But one thing you should consider is that response.isSuccessful() return true most of time and it doesn't mean your responseBody contains data. according to document:
isSuccessful() Returns true if code() is in the range [200..300).
So the better way is to check that your response list contains data or not.
But as a better way (a bit difficult if you are beginner) is using RxJava/RxAndroid. you can process your call just one after other with flatMap.
What is an asynchronous task? Not only for retrofit but the answer is same for all, when you use asynchronous task to write some code, it seems you allow that code to run asynchronously, that is, any time it wants to run and that too in background without disturbing any other tasks and not giving any kind of effect on main UI thread. So asynchronous task clearly defines itself as a task running in background and the code written above or below is never affected by it. So in your case it asynchronous task might not have completed or may be it is possible that it might not had started also but it does not disturb your if condition. https://developer.android.com/reference/android/os/AsyncTask.html

Handling API exceptions in RxJava

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/

Login function with ASync networking request?

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);
}

Categories

Resources