Implementing Retry button for Retrofit error on LiveData observable - android

learner here and I'm trying to add a retry button for whenever there is an error in Retrofit Callback#onFailure method.
Somewhat following the Android Architecture Guide, I'm able to call, persist the data and show it on RecyclerView. Here is a general flow of what I've done so far:
On PagedList.BoundaryCallback I'm getting the response and saving it. Here I've also created a LiveData of NetworkState, which I'm observing within MainActivity through ViewModel class.
#Override
public void onZeroItemsLoaded() {
if (isFetchNeeded())
mClient.fetchFirstNetworkCall().enqueue(getRetrofitCallback());
}
#Override
public void onItemAtEndLoaded(#NonNull Item itemAtEnd) {
if (mNextPageToken != null)
mClient.fetchNextNetworkCall(mNextPageToken).enqueue(getRetrofitCallback());
}
#Override
public void onResponse(#NonNull Call<BloggerApi> call, #NonNull Response<BloggerApi> response) {
mObservableNetwork.setValue(NetworkState.LOADING);
if (response.isSuccessful()) {
mExecutors.diskIO().execute(() -> {
insertItemsToDb(responseBody.getItems());
mObservableNetwork.postValue(NetworkState.SUCCESS);
});
} else {
String error = response.errorBody() == null ? "Unknown Error" : String.valueOf(response.errorBody());
mObservableNetwork.setValue(NetworkState.error(error));
}
}
#Override
public void onFailure(#NonNull Call<BloggerApi> call, #NonNull Throwable t) {
mObservableNetwork.setValue(NetworkState.error(t.getMessage()));
}
And then on UI:
mViewModel.getNetworkState().observe(this, networkState -> {
if (networkState.getStatus() == Status.ERROR) {
retryButton.setOnClickListener(view -> {
// todo: Implement what to do
});
}
});
I'm lost here and don't know how implement a Retry button to make the last call if for some reason I get an error. Can you please help me out about what the Retry button should actually do to get the callback?
Thank you.
P.S. I'm new to Java, and as of now Kotlin is out of my league so couldn't figured out how Google sample projects implementing the retry method, and, also my sample project is on GitHub/DemoBlogApp for any reference. Any help is appreciated.

Figured it out myself long time back but was waiting for an opinion. While I didn't got any, thought of posting an answer to myself so that others may find it useful.
Retrofit has clone() method which can be super useful for situations for failures. So basically, make an interface:
public interface RetryCallback<T> {
void getCall(Call<T> call, ApiCallback<T> callback);
}
On Retrofit failure:
class Repository {
RetryCallback<Api> retryCallback;
//...
new Callback<Api>() {
// other Callback methods
public void onFailure(Call<Api> call, Throwable t) {
retryCallback.getCall(call, this);
}
}
public void setRetryCallback(RetryCallback<Api> retryCallback) {
this.retryCallback = retryCallback;
}
}
On MainActivity:
//...
// Using lambda instead of initializing with new operator
viewModel.setRetryCallback((call, callback) ->
call.clone().enqueue(callback);
);

Related

What is the correct way to handle UnAuthenticated response (401) in Android retrofit

I want to take the user to the login screen whenever I encounter a 401 response from the server.
I am currently handling 401 like this:
public abstract class BaseCallback<T> implements Callback<T> {
private final Context context;
public BaseCallback(Context context) {
this.context = context;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.code() == 401) {
// launch login activity using `this.context`
} else {
onSuccess(response.body());
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
}
abstract void onSuccess(T response);
}
Courtesy of https://stackoverflow.com/a/49789543/6341943
But this way, I have to pass context from my ViewModel(my ViewModel is inherited from AndroidViewModel) whenever I make an API call.
Another way I found was to use an interceptor.
I feel like there should be a better way to handle this but I couldn't find something better than this.
How does Google handle this? I couldn't find any such sample apps. I tried ioshed but couldn't find anything like this.
Please point me in the right direction.

How to return the value from retrofit onResponse?

I'm a beginner with Android and have written a simple retrofit example:
LOGININTERFACE mAPISERVICE;
mAPISERVICE= LOGINGENERATOR.getAPIServices();
mAPISERVICE.savePost("0016642902","0016642902","password").enqueue(new Callback<LGOINMODEL>() {
#Override
public void onResponse(Call<LGOINMODEL> call, Response<LGOINMODEL> response) {
LGOINMODEL output=response.body();
if(response.isSuccessful())
test[0] ="behzad behzad behzad";
}
#Override
public void onFailure(Call<LGOINMODEL> call, Throwable t) {
}
});
But in this line:
test[0] ="behzad behzad behzad";
It can't return a value, and have not read any tutorials, examples, or other posts on Stack Overflow, and so still cannot solve this problem.
you need a callback so whenever you make a call to this function to get the values from the server you can pass the callback and when results are there it can give you back.
Callback interface
MyResultsListener.java
public interface MyResultsListener{
public void onData(LGOINMODEL loginModel);
public void onFailed();
}
now suppose the call you made is in some function then pass the MyResultsListener reference as a parameter to get back the results.
public void makeServerCall(MyResultsListener listener){
LOGININTERFACE mAPISERVICE;
mAPISERVICE= LOGINGENERATOR.getAPIServices();
mAPISERVICE.savePost("0016642902","0016642902","password").enqueue(new Callback<LGOINMODEL>() {
#Override
public void onResponse(Call<LGOINMODEL> call, Response<LGOINMODEL> response) {
LGOINMODEL output=response.body();
if(response.isSuccessful())
test[0] ="behzad behzad behzad";
//for getting back data to calling class or function
listener.onData(output);
}
#Override
public void onFailure(Call<LGOINMODEL> call, Throwable t) {
//for failure handling
listener.onFailed();
}
});
}
With retrofit2 is possible make synchronous call:
mAPISERVICE = LOGINGENERATOR.getAPIServices();
Callback<LOGINMODEL> mLOGINMODEL = mAPISERVICE.savePost("0016642902","0016642902","password");
Response<LOGINMODEL> response = mLOGINMODEL.execute();
...
however, synchronous requests trigger app crashes on Android 4.0 or newer. You’ll run into the NetworkOnMainThreadException error.
More information here.

Retrofit 2 + RxJava cancel/unsubscribe

I am performing a network request where I send files and a message.
I would like to have an option to cancel current request. I have found two similar questions and both suggests that observable.subscribe(Observer) returns Subscription object which has method unsubscribe().
Here is the first one
And the second one
In my case, I use observable.subscribe(Observer) which is void. Here is my code:
Observable<MessengerRaw> observable = mModel.sendMessage(message, companion, description, multiParts);
observable.subscribe(new Observer<MessengerRaw>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(MessengerRaw value) {
if (getView() != null) {
((MessengerActivity) getView()).resetMessegeView();
((MessengerActivity) getView()).updateMessageList();
}
}
#Override
public void onError(Throwable e) {
getData().remove(0);
if (getView() != null) {
((MessengerActivity) getView()).updateMessageList();
}
}
#Override
public void onComplete() {
hideProgress();
}
});
So how do I unsubscribe/cancel my request?
Thank you.
In RxJava2, you can get Disposable object in onSubscribe callback method of oserver, which you can use to dispose subscription.
In RXJava You must use subscriptions for unsubscribe
private Subscription mSubscription;
/.../
Observable<MessengerRaw> observable = mModel.sendMessage(message, companion, description, multiParts);
Subscription subscription = observable.subscribe(new Observer<MessengerRaw>() {/.../});
When you want to unsubscribe you can call
if(!subscription.isUnsubscribed()){
subscription.unsubscribe();
}
In RXJava 2 observable.subscribe(new Observer<MessengerRaw>() {/.../}); returns Disposable object, you can call dispose();

Retrofit generic response handler

I wish to handle all my responses in single method. the purpose is to recall the service when the response code is not 3, when the response code is 3 I intend to first refresh the token and then recall the same service.
I've created a BaseCallback class to catch one method but I can't see the log and can't catch breakpoints.
BASECALLBACK.class
public class BaseCallBack<T> implements Callback<T> {
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (!response.isSuccessful()){
Log.d("BaseCallback","Response Fail");
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
t.toString();
}
}
CALL METHOD
ApiManager.getInstance().getUsers().enqueue(new BaseCallBack<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()){
}
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
}
});
I just want to handle my services single method.
Your starting point is good - you have an ApiManager which is the single point you're looking for - a class, NOT a method (methods shouldn't be a single contact point in this case, it will make your code unreadable and harder to debug later.
From here it would probably be better to use your own custom interface, and implement it however you wish from where you call the request, there you can handle the stuff you want, this is a very generic example that should fix some stuff and get you going.
Be mindful to the fact that this still requires you to work - tweak and add the stuff you need.
This is all you need as an interface (very basic, you can add stuff)
public interface CustomCallListener<T>
{
public void getResult(T object);
}
This is how you should use it in you ApiManager - it receives your interface as a parameter carrying the expected object type, when the response returns do what you need - parse it, cut it, whatever - and cast it into the correct object, this example uses a String response and a List return object, you can expect whatever you think and parse it accordingly, Retrofit2 allows you to parse JSON strings directly (using GSON or some other library), so it's your decision on what to use here - if you don't know what I mean - read about it.
This is also where I would add breakpoints and Log. calls to debug the response you get as you get it you can also break down rawResponse for headers and other stuff.
class ApiManager
{
// .. other stuff you need...
public void getSomeList(final CustomCallListener<List<SomeObject>> listener)
{
Call<ResponseBody> request = serviceCaller.getSomeInfo(userid);
//...other stuff you might need...
request.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
//...other stuff you might need...
//...do something with the response, convert it to
//return the correct object...
SomeObject object = new SomeObject(response);
listener.getResult(object);
}
catch (Exception e)
{
// .. the response was no good...
listener.getResult(null);
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
// .. the response was no good...
listener.getResult(null);
}
});
}
}
Finally this is what you should use from anywhere in your code - this allows you to implement the callback right there and handle anything you need by what you return from the ApiManager.
ApiManager.getInstance().getSomeList(new CustomCallListener<List<SomeObject>>()
{
#Override
public void getResult(List<SomeObject> objects)
{
if (null != objects)
{
// check objects and do whatever...
}
else
{
// ... do other stuff .. handle failure codes etc.
}
}
});
Stuff to notice
As mentioned - this is a very generic skeleton that can be greatly modified (add more methods to the interface with different return types to handle Exceptions, Bad responses, other objects, add more params to the same method to handle more options, etc.) read about the subject more, beware of passing null Objects, use try and catches to avoid crashes.
Hope this Helps!

Canceling Retrofit requests or handling them when leaving the activity or fragment - android

I use Retrofit library to handle JSON response from my WCF web-service.
RestService.getRestService().search("tools", new Callback<SearchResult>() {
#Override
public void success(SearchResult searchResult, Response response) {
if( searchResult == null) return;
textView.setText(searchResult.toString());
} // end of success(
#Override
public void failure(RetrofitError error) {
showToast(R.string.internet_sikintisi);
}
});
I noticed that i get error if i leave the fragment or activity where i called this function from. Because I set text in textView where the activity or fragment is not already exists.
I modified the code to be like this:
RestService.getRestService().search("tools", new Callback<SearchResult>() {
#Override
public void success(SearchResult searchResult, Response response) {
if( searchResult == null) return;
try{
textView.setText(searchResult.toString());
} catch(Exception e) {}
} // end of success(
#Override
public void failure(RetrofitError error) {
try{
showToast(R.string.internet_sikintisi);
} catch(Exception e) {}
}
});
Basically, the problem solved, the app is not crashing every time the user gets in and out immediately. Still there might be a better solution for this problem.
I checked and Retrofit DOES NOT have cancel request feature!
You need to added checks success and failure methods the same way you would when using an asynctask check if the context still exist or make a isActive field that you set on onResume/onPause of the activity or fragment.
It is possible to cancel Retrofit requests in Retrofit 2(beta is available now.)
call.cancel();// as simple as that

Categories

Resources