From the server, sometimes we are getting a response that is null or empty. Because of this, our code will crash at some null pointer exception. We don't want to have null checks everywhere in code. Is there a way to specify default values when a retrofit response is null or empty? In the code below, this is how we can handle it on a case-by-case basis. But, we do not want to write this logic for every object. We want to do this somehow at the application level. Is that possible?
#SerializedName("isPdf")
#Expose
private String isPdf;
public boolean getIsPdf() {
return !Util.isNullOrEmpty(isPdf) && isPdf.equalsIgnoreCase("true") ? true : false;
}
public void setIsPDF(boolean isPdf) {
this.isPdf = isPdf ? "true" : "false";
}
You can create a default callback that handles a null response the way you want. For example, calling onFailure:
public class DefaultCallback<T> implements Callback<T> {
private static final String TAG = "YOUR_TAG";
private Callback<T> callback;
public DefaultCallback(Callback<T> callback) {
this.callback = callback;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.body() == null) {
callback.onFailure(call, new NullPointerException("Empty response"));
} else {
callback.onResponse(call, response);
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
Log.e(TAG, t.toString());
callback.onFailure(call, t);
}
}
And then use this callback in your implementation.
Call<MyObject> call = ... //Create your call
call.enqueue(new DefaultCallback<>(new Callback<MyObject>() {
#Override
public void onResponse(Call<MyObject> call, Response<MyObject> response) {
//Handle successful non-null responses
}
#Override
public void onFailure(Call<MyObject> call, Throwable t) {
//Handle errors/null responses
}
}));
Related
I'm implementing the paging library 2.1.2 in my Android app, my DataSource class looks like this
public class TransactionsDataSource extends PageKeyedDataSource<Integer, Items> {
private static final int FIRST_PAGE = 1;
private Context context;
private Repository repository;
private String bearerToken;
private int merchantId;
public TransactionsDataSource(Context context, int merchantId) {
this.context = context;
this.merchantId = merchantId;
repository = new Repository();
repository.initLoginSharedPref(context);
bearerToken = repository.getAccessToken();
}
#Override
public void loadInitial(#NonNull PageKeyedDataSource.LoadInitialParams<Integer> params, #NonNull PageKeyedDataSource.LoadInitialCallback<Integer, Items> callback) {
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, FIRST_PAGE)
.enqueue(new Callback<MainResponse>() {
#Override
public void onResponse(Call<MainResponse> call, Response<MainResponse> response) {
if (response.body() != null){
int code = response.body().getCode();
if (code == SERVER_OK_CODE){
callback.onResult(response.body().getData().getItems(), null, FIRST_PAGE + 1);
} else if (code == SERVER_UNAUTHENTICATED_CODE){
repository.clearCache();
HelperMethods.signOut(context);
} else {
//no more data to load
}
}
}
#Override
public void onFailure(Call<MainResponse> call, Throwable t) {
}
});
}
#Override
public void loadBefore(#NonNull PageKeyedDataSource.LoadParams<Integer> params, #NonNull PageKeyedDataSource.LoadCallback<Integer, Items> callback) {
}
#Override
public void loadAfter(#NonNull PageKeyedDataSource.LoadParams<Integer> params, #NonNull PageKeyedDataSource.LoadCallback<Integer, Items> callback) {
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, params.key)
.enqueue(new Callback<MainResponse>() {
#Override
public void onResponse(Call<MainResponse> call, Response<MainResponse> response) {
if (response.body() != null){
int code = response.body().getCode();
if (code == SERVER_OK_CODE){
Integer key = params.key + 1;
callback.onResult(response.body().getData().getItems(), key);
} else if (code == SERVER_UNAUTHENTICATED_CODE){
repository.clearCache();
HelperMethods.signOut(context);
} else {
//no more data to load
}
}
}
#Override
public void onFailure(Call<MainResponse> call, Throwable t) {
}
});
}
}
As per my knowledge the loadInitial() method is reponsible for loading the first page of data and then loadAfter() will be triggered after that to load the rest of the pages while the key is incremented by one until there's no more data to be loaded. But when I debug the app the loadAfter() is continously repeat loading the first page only even I make sure to increment the params.key() by 1. I even tried to hard code the page number as following but still loading the first page only
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, 2)
I need to know what's happening and how to fix this bug?
Although my implementation is 100% correct and After I tried so many things finally I got my way to solve this weird behavior. I made a small change in my interface instead of passing the page number to getMerchantTransactions() I decided to send the whole url as a parameter and it worked fine
here's my method in interface after update
#POST
#FormUrlEncoded
Call<MainResponse> getMerchantTransactions(#Header("Authorization") String bearerToken,
#Field("id") int merchantId,
#Url String url);
And then calling this method in loadInitial() and loadAfter() like this
ApiClient.getApiClient().getApiInterface().getMerchantTransactions(bearerToken,
merchantId, "myUrl?pagenumber=" + params.key)
In my first android project i made an onClick event to call a function:
public void doSomething(View v) {
String result = authenticate();
[...]
}
This function calls the method:
private String authenticate() {
OkHttpClient client = new OkHttpClient();
[...]
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
[...]
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseString = response.body().string();
try {
JSONObject responseObject = new JSONObject(responseString);
String responseObjectAccessToken = responseObject.getString("accesstoken");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
});
}
now i would like to return the responseObjectAccessToken to my doSomething function. A detailed explanation would be great since I am new to Java and Android Studio.
One suggestion is to handle that async response through a callback which is passed as an argument to your authenticate method. Here I'm using Java 8 lambda for that one method interface.
public class MyActivity extends Activity {
public interface AuthCallback {
void onAuthResult(String token);
}
private String authenticate(AuthCallback callback) {
// ...
String responseObjectAccessToken = responseObject.getString("accesstoken");
callback.onAuthResult(responseObjectAccessToken)
// ...
}
public void doSomething(View v) {
authenticate((token) -> {
// do something with token
});
}
// ...
}
As you can see that callback could be stored if you wanted to:
// ...
AuthCallback cb = new AuthCallback() {
#Override
public void onAuthResult(String token) {
// do something in the view/fragment/activity
}
}
// then pass it as argument
or your class could implement this interface and pass itself into the method:
public class MyActivity extends Activity implements AuthCallback {
#Override
public void onAuthResult(String token) {
// do something
}
// ...
public void doSomething(View v) {
authenticate(MyActivity.this); // <-- pass itself
}
}
One important point here is that networking happens on a separate thread, so if you want to have some UI changes after your API responds you could use a helper method runOnUiThread to do changes on main ui thread instead:
authenticate((token) -> {
runOnUiThread(() -> {
// do something with UI here
})
});
i try this for just 1 time create retrofit but i have error
i want call my retrofit class and give endPoint of url and body class , and get body from server clearly
ApiClient
public class ApiClient {
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(App.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
ApiService
public interface ApiService {
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
}
Retrofit Object
public class Request<C,T> {
private C c = null;
public C rest(T body, String urlEndPoint) {
ApiService apiService = ApiClient.getClient().create(ApiService.class);
Call<C> call = apiService.request(body, urlEndPoint);
call.enqueue(new Callback<C>() {
#Override
public void onResponse(Call<C> call, Response<C> response) {
if (response.isSuccessful())
c = response.body();
else
Toaster.shorter(App.context.getString(R.string.serverError));
#Override
public void onFailure(Call<C> call, Throwable t) {
Toaster.shorter(App.context.getString(R.string.connectionError));
}
});
return c;
}
}
calling method:
private void requestForCode() {
Request request = new Request();
int i = (int) request.rest(App.car, "/Rest/ReturnActivationCode");
if (i == 0)
Toaster.longer(App.context.getString(R.string.validateYourNumber));
else
Toaster.shorter(App.context.getString(R.string.serverError));
}
error:
12-05 12:18:04.119 773-907/? E/ConnectivityService: RemoteException caught trying to send a callback msg for NetworkRequest [ id=535, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
12-05 12:18:09.575 10359-10359/com.rayanandisheh.peysepar E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rayanandisheh.peysepar, PID: 10359
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<C>
for method ApiService.request
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:746)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:229)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.request(Unknown Source)
retrofit don't support generic objects???
It seems that you're trying to minimize your boilerplate by having a generic function to be called, but there's a better way to do this.
First, you're encapsulating the retrofit setup with your:
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
And then you're calling it with the function you created:
request.rest(App.object1, "endpoint");
But actually, this will just make things complicated and the code is very tightly coupled. You will still need to call the same method on every different APIs (request.rest(App.object2, "endpoint2"), request.rest(App.object3, "endpoint3")). This also limits the capability of retrofit (such as multiple params, customize headers, etc). What you can do is just follow the setup of retrofit:
#POST("yourendpoint")
Call<YourObjectResp> saveObject(#Body YourObjectParam param)
And to minimize your boilerplate, I suggest to make it functional:
Call<YourObjectResp> call = apiService.saveObject(new YourObjectParam());
call.enqueue(new ApiServiceOperator<>(new
ApiServiceOperator.OnResponseListener<YourObjectResp>() {
#Override
public void onSuccess(YourObjectResp body) {
// do something with your response object
}
#Override
public void onFailure(Throwable t) {
// here, you can create another java class to handle the exceptions
}
}));
And for your ApiServiceOperator.java:
/**
* Handles retrofit framework response.
* Extract the body if success, otherwise throw an exception.
*/
public class ApiServiceOperator<T> implements Callback<T> {
interface OnResponseListener<T> {
void onSuccess(T body);
void onFailure(Throwable t);
}
private OnResponseListener<T> onResponseListener;
public ApiServiceOperator(OnResponseListener<T> onResponseListener) {
this.onResponseListener = onResponseListener;
}
#Override
public void onResponse(#NonNull Call<T> call, #NonNull Response<T> response) {
if (response.isSuccessful()) { // here, do the extraction of body
onResponseListener.onSuccess(response.body());
} else {
onResponseListener.onFailure(new ServerErrorException());
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
onResponseListener.onFailure(new ConnectionErrorException());
}
// these exception can be on a separate classes.
public static class ServerErrorException extends Exception {
}
public static class ConnectionErrorException extends Exception {
}
}
With these setup, you still minimize your boilerplate and also, it makes thing reusable, scalable, and testable. ApiServiceOperator also is loosely couple with Android Context and instead, throws a plain java exception, in which, you can create a function that knows Android Context to get the appropriate message base on the exception thrown.
I have a problem with Retrofit and duplicate checking.
I have to check every time response status code or type!
I need a wrapper for request method that checks there this duplicate works.
(duplicate works includes: showLoading(),response.code(),onFailure() handle...).
I need a GenericMethod for this:
UserService service = RetrofitInstance.getRetrofitInstance().create(UserService.class);
service.RequestVerification(token, mobileNumber).enqueue(new Callback<ClientData<User>>() {
#Override
public void onResponse(#NonNull Call<ClientData<User>> call, #NonNull Response<ClientData<User>> response) {
doAction();//Action must passed to this method.
GeneralTools.hideLoading();
}
#Override
public void onFailure(#NonNull Call<ClientData<User>> call, #NonNull Throwable t) {
GeneralTools.hideLoading();
dialogError.show();
}
});
Try below
private static class CallbackHandler<T> implements Callback<T> {
#Override
public void onResponse(Call<T> call, Response<T> response) {
int code = response.code();
if (code >= 200 && code < 300) {
onSuccess(response);
} else if (code == 401) {
// logic to refresh token or user then recall the same api
call.clone().enqueue(this);
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
}
public void onSuccess(Response<T> response) {
}
}
Then change your call like below
service.RequestVerification(token, mobileNumber).enqueue(new CallbackHandler<ClientData<User>>() {
#Override
public void onSuccess(Response<ClientData<User>> response) {
doAction();//Action must passed to this method.
GeneralTools.hideLoading();
}
#Override
public void onFailure(#NonNull Call<ClientData<User>> call, #NonNull Throwable t) {
GeneralTools.hideLoading();
dialogError.show();
}
});
I'm trying to build a library that basically wraps our api. Basically, the structure im going for is something like this:
MySDK mySDK = new MySDK("username", "password");
mySDK.getPlaylistInfo("3423", 2323, new CustomCallback<>(){
//on response
//on failure
});
So with vanilla Retrofit, an api call usually looks something like the following:
ApiService api = retrofit.create(ApiService.class);
Call<Response> call = api.getPlaylistInfo()
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, Response<Response> response) {
//handle response
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
//handle failure
}
});
Basically, how would I wrap retrofits callback system into my own? Note, the reason for needing to do this is to preprocess the data returned from the api before delivering the final response.
I've written something similar so it might help you getting started, this follows an implementation I'v written for Volley, and re-used when I migrated to Retrofit2 so it resembles it (this SO question).
Create a global object (what you would refer to as MySDK) as a singelton class that handles your requests:
create a singleton class, which you instatiate when you're application comes up:
public class NetworkManager
{
private static final String TAG = "NetworkManager";
private static NetworkManager instance = null;
private static final String prefixURL = "http://some/url/prefix/";
//for Retrofit API
private Retrofit retrofit;
private ServicesApi serviceCaller;
private NetworkManager(Context context)
{
retrofit = new Retrofit.Builder().baseUrl(prefixURL).build();
serviceCaller = retrofit.create(ServicesApi.class);
//other stuf if you need
}
public static synchronized NetworkManager getInstance(Context context)
{
if (null == instance)
instance = new NetworkManager(context);
return instance;
}
//this is so you don't need to pass context each time
public static synchronized NetworkManager getInstance()
{
if (null == instance)
{
throw new IllegalStateException(NetworkManager.class.getSimpleName() +
" is not initialized, call getInstance(...) first");
}
return instance;
}
public void somePostRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
String url = prefixURL + "this/request/suffix";
Map<String, Object> jsonParams = new HashMap<>();
jsonParams.put("param1", param1);
Call<ResponseBody> response;
RequestBody body;
body = RequestBody.create(okhttp3.MediaType.parse(JSON_UTF), (new JSONObject(jsonParams)).toString());
response = serviceCaller.thePostMethodYouWant("someUrlSufix", body);
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
public void someGetRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
// you need it all to be strings, lets say id is an int and name is a string
Call<ResponseBody> response = serviceCaller.theGetMethodYouWant
(String.valueOf(param1.getUserId()), param1.getUserName());
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
}
This works with your interface (example with POST and GET request, GET could be without params):
public interface BelongServicesApi
{
#POST("rest/of/suffix/{lastpart}") // with dynamic suffix example
Call<ResponseBody> thePostMethodYouWant(#Path("lastpart") String suffix, #Body RequestBody params);
#GET("rest/of/suffix") // with a fixed suffix example
Call<ResponseBody> theGetMethodYouWant(#Query("userid") String userid, #Query("username") String username);
}
when your application comes up:
public class MyApplication extends Application
{
//...
#Override
public void onCreate()
{
super.onCreate();
NetworkManager.getInstance(this);
}
//...
}
a simple listener interface for your callback (seperate file would do good):
public interface SomeCustomListener<T>
{
public void getResult(T object);
}
and finally, from wherever you want, the context is already in there, just call:
public class BlaBla
{
//.....
public void someMethod()
{
//use the POST or GET
NetworkManager.getInstance().somePostRequestReturningString(someObject, new SomeCustomListener<String>()
{
#Override
public void getResult(String result)
{
if (!result.isEmpty())
{
//do what you need with the result...
}
}
});
}
}
you can use any object with the listener, just parse the response string to a corresponding object, depending on what you need to receive and you can call that from everywhere (onClicks, etc.), just remember the objects need to match between methods.
Hope this Helps!