Avoiding Memory Leak while referencing Activity from onResponse of Retrofit2 callback - android

Suppose we have a retrofit Callback as follows:
Callback<T> callback = new Callback<T>() {
#Override
public void onResponse(Call<T> call, Response<T> response) {
mProgressBar.dismiss();
if (response.code() == 401) {
Toast.makeText(getApplicationContext(),
"Invalid session, logging out",
Toast.LENGTH_LONG).show();
Session.getInstance(getApplicationContext()).clear();
this.navigateToLoginActivity();
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
}
}
Suppose the retrofit request with this callback runs for a long time and if the activity starting the request has already finished by the time onResponse is invoked, then it will cause memory leak we are referring to the activity via
this.navigateToLoginActivity()
So the question is how to get the context in onResponse? WeakReference? But this is such a common use case, i.e. to logout user, in case, the authentication fails. I want to know the best practice in this scenario for a general AsyncTask case and retrofit case as well.

Related

Retrofit Call.enqueue() send requests in the order that were created?

Imagine this scenario:
I start a requestA using the Call.enqueue() method, then, before requestA be finished, I start requestB at the same endpoint of requestA. While I'm using Call.enqueue() method, requestB will be executed after requestA? Or enqueue() method is just used to do requests asynchronously?
I search that information at docs and here on StackOverflow but all the information is superficial about this specific method.
Here is my code - this same code is used for both requests:
foolRequest.enqueue(new Callback<Response>() {
#Override
public void onResponse(#NonNull Call<Response> call,
#NonNull retrofit2.Response<Response> response) {
//do something
}
#Override
public void onFailure(#NonNull Call<Response> call,
#NonNull Throwable t) {
//do something
}
});
I think so,
Otherwise, if you implement your own connection client.
By the source code from OkHttpClient, there is a dispatcher class, save all the enqueue API, and it uses queue to save the relative task
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

Retrofit cancel() & check if request was delivered to server

How can I be sure that calling cancel() on the Retrofit cause request is not delivered to the server?
Currently I know, that calling cancel on the Retrofit will invoke onFailure branch of the callback. And then I can check if the cause of the failure is because call was cancelled.
https://futurestud.io/tutorials/retrofit-2-cancel-requests
But can I be somehow sure, that request didn't arrive to the server?
Code snippet from here
new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.d(TAG, "request success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
if (call.isCanceled()) {
Log.e(TAG, "request was cancelled");
}
else {
Log.e(TAG, "other larger issue, i.e. no network connection?");
}
}
};
According to that this will raise in case call was canceled, as it say
// something happened, for example: user clicked cancel button
call.cancel();
If you call call.cancel() let's say later it means you already got a response from the server, therefore you won't get to onFailure point where call.isCanceled() is being checked :)
If you cancel the request, Retrofit will classify this as a failed request and thus call the onFailure() callback in your request declaration. This callback is also used when there is no Internet connection or something went wrong on the network level. From the app perspective, these errors are quite a bit different.

How to set a value to LiveData.setValue() when loading data is not successful?

Let's say I have a User view model to load the user's data. I would use Retrofit2 to load the users data. A simple example would be like this:
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
userMutableLiveData.setValue(response.body());
} else {
//since response.body() is null, how to set a suitable value to userMutableLiveData.setValue()
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
//since there was a networking failure, how to set a suitable value to userMutableLiveData.setValue()
}
});
Now my question is when retrofit returns response code is not successful or a networking failure occur how do I set a suitable value so that I can give the user a good feedback of what happened.

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!

Cancel calls in retrofit:2.0.0-beta2 by tag

I use the com.squareup.retrofit:retrofit:2.0.0-beta2 and encounter some problem.
I want to add the possibility to close some of the downloading by button press. So I found the cancel(Object tag) method in OkHTTPClient.
I tried to find the place where I can put this tag value but didn't found anything. Also passing null as a parameter not work at all.
Could someone help to tell me where I can put tag or suggest another approach?
Retrofit2 has a cancel() method as well. You can use that. Here is an example:
Call<ResponseBody> call =
downloadService.downloadFileWithDynamicUrlSync(fileUrl);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.d(TAG, "request success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, "request failed");
}
});
// something happened, for example: user clicked cancel button
call.cancel();
Be aware that if you cancel the request, Retrofit will classify it as a failure & call onFailure().
Further reading in case you're interested: https://futurestud.io/blog/retrofit-2-cancel-requests

Categories

Resources