I have a fragment with a nested retrofit call inside an on response callback. I am cancelling both requests on the onStop method of the fragment, which according to the logs and debugging it's being call thus the cancel() for both retrofit calls are being called too.
Here is the code for the nested calls
serviceRequestTypesResponseCall.enqueue(new RetrofitCallback<ServiceRequestTypesResponse>(retrofit) {
#Override
public void onResponse(RetrofitResult<ServiceRequestTypesResponse> result) {
// get url from response and enqueue nested call
detailedServiceRequestTypeCall = scfServiceV2.getRequestType(result.getRequestTypeUrl());
detailedServiceRequestTypeCall.enqueue(new RetrofitCallback<DetailedServiceRequestType>(retrofit) {
#Override
public void onResponse(RetrofitResult<DetailedServiceRequestType> result) {
// this is being reached when cancelled
}
#Override
public void onFailure(RetrofitResult<DetailedServiceRequestType> error) {
// do something
}
});
}
#Override
public void onFailure(RetrofitResult<ServiceRequestTypesResponse> error) {
// do something
}
});
public void onStop() {
super.onStop();
// nested call
if(detailedServiceRequestTypeCall != null) {
detailedServiceRequestTypeCall.cancel();
}
// outer call
if(serviceRequestTypesResponseCall != null) {
serviceRequestTypesResponseCall.cancel();
}
}
The nested call gets a dynamic url from the outer call response, thus this is how it's defined in it's retrofit interface
#GET
Call<DetailedServiceRequestType> getRequestType(#Url String url);
also, this is how I am handling Retrofit cancellation in my custom callback class
public abstract class RetrofitCallback<T> implements Callback<T> {
private static final String TAG = "RetrofitCallback";
protected final Retrofit retrofit;
public RetrofitCallback(Retrofit retrofit) {
this.retrofit = retrofit;
}
public abstract void onResponse(RetrofitResult<T> result);
public abstract void onFailure(RetrofitResult<T> error);
#Override
public final void onResponse(Call<T> call, Response<T> response) {
onResponse(new RetrofitResult<>(retrofit, response));
}
#Override
public final void onFailure(Call<T> call, Throwable t) {
if (call.isCanceled()) {
Log.d(TAG, "Cancelled => " + call.request().toString());
} else {
onFailure(new RetrofitResult<T>(t));
}
}
}
The fragment is inside a bottombar navigation (https://github.com/roughike/BottomBarbottombar) which basically replaces the fragments upon tab selection. The requests are happening when the fragment gets displayed so switching the fragments quickly so to not give time for the nested call to finish reproduces this issue.
The logcat only shows the cancel log for the outer call. Not sure why I am not getting the same for the nested call despite being explicitly cancelled . This causes the request to finish and try to do some ui related logic and reference the activity which is no longer available because the fragment has been replaced when switching fragments
Related
I've been struggling for many hours on how to do this... So I have an Activity which creates a fragment.
mAddCommentButton.setOnClickListener((View v) ->{
BottomSheetAddComment bottomSheetAddComment = new BottomSheetAddComment();
bottomSheetAddComment.show(getSupportFragmentManager(), null);
});
In that fragment, it makes a network call and I want to send the results of that network call back to the Activity's Presenter, but I can't seem to understand how to do it...
private void makeNetworkCall(Comment comment){
RetrofitInterfaces.IPostNewComment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IPostNewComment.class);
Call<EventCommentsDao> call = service.listRepos(comment);
call.enqueue(new Callback<EventCommentsDao>() {
#Override
public void onResponse(Call<EventCommentsDao> call, Response<EventCommentsDao> response) {
// Send response back to Activity Presenter
}
#Override
public void onFailure(Call<EventCommentsDao> call, Throwable t) {
}
});
}
Presenter:
public class EventPresenter implements EventContract.Presenter{
private EventContract.View eventView;
private EventContract.Model eventModel;
public EventPresenter(EventContract.View eventView) {
this.eventView = eventView;
eventModel = new EventModel();
}
#Override
public void onDestroy() {
this.eventView = null;
}
#Override
public void requestDataFromServer() {
if(eventView != null){
eventView.hideProgress();
}
eventModel.getEventInfo(this);
}
}
How do I get reference to the Activity Presenter so I can send the results back?
Add a method in your Activity to return event presenter:
public EventPresenter getPresenter() {
return this.eventPresenter;
}
And in your Fragment:
private void makeNetworkCall(Comment comment){
RetrofitInterfaces.IPostNewComment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IPostNewComment.class);
Call<EventCommentsDao> call = service.listRepos(comment);
call.enqueue(new Callback<EventCommentsDao>() {
#Override
public void onResponse(Call<EventCommentsDao> call, Response<EventCommentsDao> response) {
// get your presenter by:
EventPresenter mPresenter = ((MyActivity) getActivity()).getPresenter();
}
#Override
public void onFailure(Call<EventCommentsDao> call, Throwable t) {
}
});
}
Different alternatives in terms of communication between fragments would be to create callback interfaces or use event bus. See this post for more details Android MVP : One Activity with Multiple Fragments
I am new to Android Arch components. I am trying to make a basic todo app using Android View Model and Live Data. What is the best way to make network calls when following MVVM pattern? I need to show a progress bar when a network request starts and dismiss it when the call is complete and in case of errors I need to display a snackbar with the relevant error message. Is it possible to do this without using an AsyncTask?
Remote Repository Class:
public class RemoteRepository {
private APIService apiService;
public RemoteRepository (APIService apiService) {
this.apiService= apiService;
}
public LiveData<List<Project>> getList(String userId) {
final MutableLiveData<List<Project>> data = new MutableLiveData<>();
apiService.getList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
data.setValue(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
// What to do to show snackbar in activity
}
});
return data;
}
Do we need to use an async task and manage the UI in its preExecute and postExecute callbacks? Or is it possible to do it with Observable Livedata objects?
You can do it this way.
Create a class that has a Throwable and desire success result object.
create a constructor or getter setter method to set values. ProjectRepoClass is an example of it.
Project Repo Class::
public class ProjectRepoModel {
private List<Project> mList;
private Throwable mThrowable;
public ProjectRepoModel (List<Project> mList) {
mList= mList;
}
public ProjectRepoModel (Throwable throwable) {
mThrowable = throwable;
}
public List<Project> getList() {
return mList;
}
public Throwable getThrowable() {
return mThrowable;
}
}
Set value according to API result. it can be an error or success response and return it.
Return data:
public LiveData<List<Project>> getList(String userId) {
final MutableLiveData<ProjectRepoModel > data = new MutableLiveData<>();
apiService.getList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
data .setValue( new ProjectRepoModel (response.body()));
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
data .setValue( new ProjectRepoModel (t));
}
});
return data;
}
Create Observable for live data in the UI part and make a check for error and display results according to it.
Observe result In UI Like this:
if (mModel.getThrowable() != null)
{
// Show error
} else {
// display success result
}
This how you can achieve the error handle from the repository in MVVM.
I had a retrofit request, when I get data in onResponse,
I did multiples insert in textviews which I called heavy work in the code above, I get the result from OnReponse if there's one, else I get result from database, so the problem I had the same code in OnResponse and OnFailure, so there's any way to put my heavy work outside retrofit, and wait the response to get just one result from OnResponse or OnFailure ??
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(response.body());
realm.commitTransaction();
// heavy work : insert in data in multiple text views
}
#Override
public void onFailure(Call<Dashboard> call, Throwable t) {
Log.e("error ", "" + t.getMessage());
dashboard = realm.where(Dashboard.class).findFirst();
// heavy work : insert in data in multiple text views
}
}
Try this..
First Create an interface ..Let's call it OKCallback.
public interface OKCallback {
void onSuccess(String result);
void onFailure(String result);
}
Then in your method that launches the retrofit request, pass final OKCallback okCallback like this..
public void NetworkCall(final OKCallback okCallback){
...........
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(response.body());
realm.commitTransaction();
// heavy work : insert in data in multiple text views
okCallback.onSuccess(parcel);
}
Finally simply (ActivityX implements OKCallback) in any class or activity and you should be able to do your heavy work there..You can also wrap your data in the onSuccess methods with a Handler as shown.
#Override
public void onSuccess(String result) {
Handler handler = new Handler(ActivityX.this.getMainLooper());
//process result and
handler.post(new Runnable() {
#Override
public void run() {
//heavy work done here will run on UI thread
}
});
}
You can make an interface and get call back on main thread or after getting response of api call in onSuccess() or in onfailure() start a new AsynTask and process the request in background.
You can change it like this
//create a interface
public interface ConfirmationCallback {
void onSuccess(YourResponseClass value);
void onError();
}
//call this method from your class
yourApiCall(new ConfirmationCallback() {
#Override
public void onSuccess(YourResponseClass value) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(value);
realm.commitTransaction();
// heavy work : insert in data in multiple text views
}
#Override
public void onError() {
dashboard = realm.where(Dashboard.class).findFirst();
// heavy work : insert in data in multiple text views
}
});
public void yourApiCall(final ConfirmationCallback confirmationCallback){
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
confirmationCallback.onSuccess(response.body());
}
#Override
public void onFailure(Call<Dashboard> call, Throwable t) {
Log.e("error ", "" + t.getMessage());
confirmationCallback.onError();
}
}
}
I have a fragment set up like so:
public mFragment extends Fragment implements Callback<mType> {
...
#Override
public void onViewCreated(View v, Bundle sis) {
Retrofit retrofit = new Retrofit.Builder().baseUrl("MYURL").addConverterFactory(GsonConverterFactory.create()).build();
api mAPI = retrofit.create(api.class);
Call<mType> call1 = mAPI.query1("query1"));
Call<mType> call2 = mAPI.query2("query2"));
call1.enqueue(this);
call2.enqueue(this);
}
#Override
public void onFailure(Throwable t) {
...
}
#Override
public void onResponse(final Response<mType> response, Retrofit retrofit) {
...
}
}
I need to make 2 api calls, which both return the same type. However, I want to handle both of them in different onResponse methods as I need to do distinct things to both of them. This is under Retrofit 2.0.
This is an API of a different service, so I do not have access to change any of the responses.
Is there a way to specify which method a Retrofit Call calls back to? I'm really hoping that this has as clean of a solution as if I were using two different return types. If worst comes to worst I can just duplicate the object and rename it but I think there is a "correct" way to do this.
queue your requests separately. So your response listeners will be separate for both the requests
call1.enqueue(new Callback<String>() {
#Override
public void onResponse(Response<String> response) {
}
#Override
public void onFailure(Throwable t) {
}
});
and
call2.enqueue(new Callback<String>() {
#Override
public void onResponse(Response<String> response) {
}
#Override
public void onFailure(Throwable t) {
}
});
I am migrating from Volley to a custom implementation using Retrofit, but I'm trying to add to my implementation some of the Volley features that I liked, for example
RequestQueue.cancel(String tag)
If the Request has the requested tag, then it's canceled by setting a boolean value, mCanceled, to true. The run method checks this value and returns if it's true.
To be able to reproduce this with Retrofit I should be able to use my custom class implementing Runnable instead of the default one, where I have a mTag and a mCanceled field.
Moreover, Volley was also able to set such flag inside the active Threads and immediately stop them. My cancelAll method, that I've already implemented, just drains the queue to another queue, but isn't able to access the active threads.
Is it possible to achieve the same results with Retrofit and ThreadPoolExecutor?
I think I've found a nicer solution: instead of blocking the Runnable of the requests, I am blocking the Callback execution.
I have extended the Callback interface:
public interface CustomCallbackInterface<T> extends Callback<T> {
public String getTag();
public String setTag(String tag);
public void cancel();
public boolean isCanceled();
}
so that each Callback has a tag and a cancel flag. Then the success method starts with:
public class CustomCallback<ConvertedData> implements CustomCallbackInterface<ConvertedData>{
//failure...
#Override
public void success(ConvertedData cd, Response response) {
if(isCanceled()) return;
// ....
}
}
Every time I make a new request, I store the created CustomCallback inside a List cancel just iterates the list and calls cancel() on the items with the same tag.
I've implemented an easy to use class based on Vektor88 answer
public abstract class CancelableCallback<T> implements Callback<T> {
private static List<CancelableCallback> mList = new ArrayList<>();
private boolean isCanceled = false;
private Object mTag = null;
public static void cancelAll() {
Iterator<CancelableCallback> iterator = mList.iterator();
while (iterator.hasNext()){
iterator.next().isCanceled = true;
iterator.remove();
}
}
public static void cancel(Object tag) {
if (tag != null) {
Iterator<CancelableCallback> iterator = mList.iterator();
CancelableCallback item;
while (iterator.hasNext()) {
item = iterator.next();
if (tag.equals(item.mTag)) {
item.isCanceled = true;
iterator.remove();
}
}
}
}
public CancelableCallback() {
mList.add(this);
}
public CancelableCallback(Object tag) {
mTag = tag;
mList.add(this);
}
public void cancel() {
isCanceled = true;
mList.remove(this);
}
#Override
public final void success(T t, Response response) {
if (!isCanceled)
onSuccess(t, response);
mList.remove(this);
}
#Override
public final void failure(RetrofitError error) {
if (!isCanceled)
onFailure(error);
mList.remove(this);
}
public abstract void onSuccess(T t, Response response);
public abstract void onFailure(RetrofitError error);
}
Usage example
rest.request(..., new CancelableCallback<MyResponse>(TAG) {
#Override
public void onSuccess(MyResponse myResponse, Response response) {
...
}
#Override
public void onFailure(RetrofitError error) {
...
}
});
// if u need to cancel all
CancelableCallback.cancelAll();
// or cancel by tag
CancelableCallback.cancel(TAG);