Perform requests with Retrofit inside custom Runnable - android

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

Related

How to send data from Fragment back to Activity using MVP pattern

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

Retrofit nested call not being cancelled

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

Configuring RxJava to Send Data to activity from GCMListenerService

I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}

RxJava with multi network request

Here is the code:
public class HomeDetails extends Model {
public Home mHomeData;
public AD mAdData;
public HomeDetails(Api api, String url) {
api.getHome(url, createHome(), this);
api.getAd(url, createAD(), this);
}
private NetworkResponse.Listener<Home> createHome() {
return new NetworkResponse.Listener<Home>() {
#Override
public void onResponse(Home home) {
mHomeData = home;
}
};
}
private NetworkResponse.Listener<AD> createAD() {
return new NetworkResponse.Listener<AD>() {
#Override
public void onResponse(AD ad) {
mAdData = ad;
}
};
}
}
I'd like to use RxJava to help me to know when the two requests are all done. if all is done, then execute another method.
You can use Observable.create() to create the two observable for the two network calls, then you can concat() or zip() them and execute whatever you want in the onNext().

How to determine if a given request is running?

I'm looking at retrofit for my networking layer. Is there any way to tell if a particular async request is running at any given moment?
For example, I'd like to know if a request is running so that I can update the user interface at various times. I could do this myself by keeping variables around to track state, but wondering if there's something already in the library for this.
Here is what I would normally do when needing a way to keep track of running requests:
First, using retrofit, as soon as you make the request, you can do the following:
Use EventBus library to post an event to your activity or fragment. Now, this can be done inside onSuccess() method of your Callback or onError() method of the same.
In your activity or fragment's onEvent(EventClassName event) method, you can simply check a variable like [isRunning] from your event to make sure that if the event is still running, you update the UI accordingly and if not, do what you need to do respectively.
When the request is completed, obviously isRunning will be false and you can then update the UI as expected by the user.
I am recommending EventBus here simply because it is much easier to decouple your application code with it; you can send different events that notify the activity of the different statuses of your requests and then update your UI that way.
You can find EventBus here
I hope this helps!
What I personally ended up doing in this case was that I was running the example with Retrofit, Android Priority Jobqueue (from yigit's fork) and Otto eventbus.
public enum SingletonBus {
INSTANCE;
private Bus bus;
private Handler handler = new Handler(Looper.getMainLooper());
private SingletonBus() {
this.bus = new Bus(ThreadEnforcer.ANY);
}
public <T> void postToSameThread(final T event) {
bus.post(event);
}
public <T> void postToMainThread(final T event) {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
public <T> void register(T subscriber) {
bus.register(subscriber);
}
public <T> void unregister(T subscriber) {
bus.unregister(subscriber);
}
}
public interface Interactor {
void injectWith(PresenterComponent presenterComponent);
}
public interface SendCertificateRequestInteractor
extends Interactor {
interface Listener {
void onSuccessfulEvent(SuccessfulEvent e);
void onFailureEvent(FailureEvent e);
}
class SuccessfulEvent
extends EventResult<CertificateBO> {
public SuccessfulEvent(CertificateBO certificateBO) {
super(certificateBO);
}
}
class FailureEvent
extends EventResult<Throwable> {
public FailureEvent(Throwable throwable) {
super(throwable);
}
}
void sendCertificateRequest(String username, String password);
}
Pay attention to the Job here:
public class SendCertificateRequestInteractorImpl
implements SendCertificateRequestInteractor {
private Presenter presenter;
private boolean isInjected = false;
#Inject
public JobManager jobManager;
public SendCertificateRequestInteractorImpl(Presenter presenter) {
this.presenter = presenter;
}
#Override
public void sendCertificateRequest(String username, String password) {
if(!isInjected) {
injectWith(presenter.getPresenterComponent());
isInjected = true;
}
InteractorJob interactorJob = new InteractorJob(presenter, username, password);
long jobId = jobManager.addJob(interactorJob); //this is where you can get your jobId for querying the status of the task if you want
}
#Override
public void injectWith(PresenterComponent presenterComponent) {
presenterComponent.inject(this);
}
public static class InteractorJob
extends Job {
private final static int PRIORITY = 1;
private final static String TAG = InteractorJob.class.getSimpleName();
private String username;
private String password;
#Inject
public MyService myService;
public InteractorJob(Presenter presenter, String username, String password) {
super(new Params(PRIORITY).requireNetwork());
presenter.getPresenterComponent().inject(this);
this.username = username;
this.password = password;
}
#Override
public void onAdded() {
// Job has been saved to disk.
// This is a good place to dispatch a UI event to indicate the job will eventually run.
// In this example, it would be good to update the UI with the newly posted tweet.
}
#Override
public void onRun()
throws Throwable {
String certificate = myService.getCertificate(username, password);
SingletonBus.INSTANCE.postToMainThread(new SuccessfulEvent(certificate));
}
#Override
protected void onCancel() {
// Job has exceeded retry attempts or shouldReRunOnThrowable() has returned false.
Log.e(TAG, "Cancelled job.");
}
#Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
// An error occurred in onRun.
// Return value determines whether this job should retry running (true) or abort (false).
Log.e(TAG, "Failed to execute job.", throwable);
SingletonBus.INSTANCE.postToMainThread(new FailureEvent(throwable));
return false;
}
}
}
And then
#Subscribe
#Override
public void onSuccessfulEvent(SendCertificateRequestInteractor.SuccessfulEvent e) {
String certificate = e.getResult();
//do things
}
#Subscribe
#Override
public void onFailureEvent(SendCertificateRequestInteractor.FailureEvent e) {
Throwable throwable = e.getResult();
//handle error
}
More about android priority jobqueue here.
This way, technically the async handling is referred to the job queue, while Retrofit itself is using the synchronous interface. It works well as long as you don't need to access the headers of the response. Although to be fair, I was also keeping track of whether the job was running with a boolean instead of the job manager and the id as well..
Also, I haven't figured out how to use dependency injection properly with persisted jobs; nor do I really know how they intended to make that work. Of course, it'd work if it was using the application scoped component rather than a supplied presenter scoped one, but that is irrelevant.
You'll probably need to customize this solution to your own scenario, and use only what you actually need.

Categories

Resources