Showing posts with label retrofit2. Show all posts
Showing posts with label retrofit2. Show all posts

Saturday, October 6, 2018

Why I can not debug Proxy.newProxyInstance method?

Leave a Comment

I am using Retrofit on Android.

I define a service GitHubService.

public interface GithubService {      @GET("users/{user}")     Call<ResponseBody> fetchUserInfo(@Path("user") String user);  } 

Then I create service.

Retrofit retrofit = new Retrofit.Builder()         .baseUrl("http://api.github.com")         .build();  GithubService service = retrofit.create(GithubService.class); Call<ResponseBody> call = service.fetchUserInfo("coxier"); call.enqueue(...); 

As you see, I use above code to get user info of github. And above code works well for me. Now I want to know how Retofit works so I read source code.The code below is Retrofit#create. After reading, I still don't know its magic then I decide to debug Retroft.

public <T> T create(final Class<T> service) {   ...   return (T) Proxy.newProxyInstance(...,       new InvocationHandler() {         ...         @Override public Object invoke(..)             throws Throwable {            if (method.getDeclaringClass() == Object.class) {             return method.invoke(this, args);           }           ...           return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);         }       }); } 

I debug at if(method.getDeclaringClass() == Object.class) line ,however it crashes. I don't know why it crashes.

EDIT

I tested several devices.

  • Nexus 6P Android 8.0 : crash when debug
  • Xiaomi 6 Android 8.1: crash when debug
  • OnePlus 3 Android 8.0: crash when debug
  • Nexus 6P Android 7.0:works well when debug
  • Oppo Android 6.0: works well when debug
  • Samsung S4 Android 5.0: works well when debug

It may crash above Android 8.0.

2 Answers

Answers 1

Since the crashes occur with Android 8 and higher, I suppose it has to do with the restrictions of that versions in regard of apps running in background.

I suppose, that due to your debugging efforts, the app stays active in background for too long, causing the system to eventually kill it.

However, there should be something in the Logcat about that. Perhaps you see it, if you remove any filters from the Logcat window.

Answers 2

Now I want to know how Retofit works so I read source code.

Is this what You were trying to achieve? By adding interceptor You will be able see the requests in logcat.

private RestApiStack() {      OkHttpClient.Builder builder = new OkHttpClient.Builder();      // preview requests     if (BuildConfig.DEBUG) {         HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();         interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);         builder.addInterceptor(interceptor);     }      OkHttpClient client = builder.build();      Retrofit retrofit = (new Retrofit.Builder())             .baseUrl(BASE_URL)             .client(client)             .addConverterFactory(/*...*/)             .build();      api = retrofit.create(ApiStack.class); } 

Dependencies:

implementation "com.squareup.okhttp3:logging-interceptor:3.9.1" implementation "com.squareup.okhttp3:okhttp:3.11.0" 
Read More

Thursday, September 27, 2018

rxjava + retrofit - How to wait and get the result of first observable in android?

Leave a Comment

I've recently started learning retrofit and rxjava. I'm looking for any ideas on how to wait ang get the result of first observable. Basically, I want to apply it on a simple login. The first api call is getting the server time. The second api call will wait the result of the first call (which is the server time) and utilize it.

                Retrofit retrofit = RetrofitClient.getRetrofitClient();                 LocalServerInterface apiInterface = retrofit.create(LocalServerInterface .class);                  Observable<ServerTime> timeObservable = retrofit                         .create(LocalServerInterface .class)                         .getTime()                         .subscribeOn(Schedulers.newThread())                         .observeOn(AndroidSchedulers.mainThread());                  Observable<ServerNetwork> serverNetworkObservable = retrofit                         .create(LocalServerInterface .class)                         .getNetworks(//valuefromapicall1, anothervalue)                         .subscribeOn(Schedulers.newThread())                         .observeOn(AndroidSchedulers.mainThread()); 

Now, I'm stuck right here. On second observable, particularly on getNetworks method, I wanted to use what I got from first observable. Any ideas?

EDIT:

I wanted to process first the result of call 1 before supplying it to the api call 2. Is it possible?

1 Answers

Answers 1

First, do not recreate LocalServerInterface each time, create one and reuse it. Creation of the instance of the interface is an expensive operation.

LocalServerInterface apiInterface = retrofit.create(LocalServerInterface.class) 

And to make the second observable start with the result of the first observable, you need to do flatMap.

Observable<ServerNetwork> serverNetworkObservable = apiInterface                         .getTime()                         .flatMap(time -> apiInterface.getNetworks(time, anothervalue))                         .subscribeOn(Schedulers.newThread())                         .observeOn(AndroidSchedulers.mainThread()); 

See the flatMap documentation for more info.

IMPORTANT NOTE. In this case, when only one response will be emitted by the first observable, there's no difference between using flatMap and concatMap. For the other cases, consider the difference between flatMap and concatMap.

Read More

Monday, July 30, 2018

Is it possible pause & resume for Retrofit multipart request?

Leave a Comment

We are using Retrofit multi-part for file uploading process.

We want pause/resume when file uploading.

I want to know its possible or not?

Code for multi-part file upload

  RequestBody requestFile =             RequestBody.create(MediaType.parse("multipart/form-data"), file);    // MultipartBody.Part is used to send also the actual file name   MultipartBody.Part body =MultipartBody.Part.createFormData("image", file.getName(), requestFile);   Call<ResponseBody> call= api.uploadFile(body); 

2 Answers

Answers 1

As suggested by Jake Wharton from Square,

There is no built-in pause or resume for retrofit.

If you want to stick on to retrofit then you may need to make your backend to support range requests. This can help you to break the connection at any time during a streaming download. You can then use the range headers in every subsequent requests to support pause or resume download.

You may also check this answer using using okhttp

Answers 2

Yes its possible. You would have to create your own request body.

   public class PausableRequestBody extends RequestBody {     private static final int BUFFER_SIZE = 2048;     private File mFile;     private long uploadedData;      public PausableRequestBody(final File file) {         mFile = file;     }      @Override     public MediaType contentType() {         return MediaType.parse("image/*");     }      @Override     public long contentLength() throws IOException {         return mFile.length();     }      @Override     public void writeTo(BufferedSink bs) throws IOException {         long fileLength = mFile.length();         byte[] buffer = new byte[BUFFER_SIZE];         FileInputStream in = new FileInputStream(mFile);          try {             int read;             while ((read = in.read(buffer)) != -1) {                 this.uploadedData += read;                 bs.write(buffer, 0, read);             }         } finally {             in.close();         }     }      public long getUploadedData() {         return uploadedData;     } } 

use append instead of write..

Use following Retrofit call

PausableRequestBody fileBody = new PausableRequestBody(file, this);         MultipartBody.Part filePart = MultipartBody.Part.createFormData("image", file.getName(), fileBody);          Call<JsonObject> request = RetrofitClient.uploadImage(filepart);         request.enqueue(new Callback<JsonObject>{...}); 

to cancel the request request.cancel()

to restart use the same PausableRequestBody object with the same method mentioned above

Read More

Monday, April 30, 2018

OnActivityResult activity error : Failure delivering result ResultInfo

Leave a Comment

error : Failure delivering result ResultInfo{who=null, request=100, result=-1, data=Intent { dat=content://com.android.providers.media.documents/document/image:28362 flg=0x1 }} to activity {com.projectbox.uploadfile/com.projectbox.uploadfile.MainActivity}: java.lang.NullPo

  @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {     super.onActivityResult(requestCode, resultCode, data);     if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {         android.net.Uri selectedImage = data.getData();         String[] filePathColumn = {MediaStore.Images.Media.DATA};         android.database.Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);         if (cursor == null)             return;          cursor.moveToFirst();          int columnIndex = cursor.getColumnIndex(filePathColumn[0]);         String filePath = cursor.getString(columnIndex);         cursor.close();          File file = new File(filePath);          RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);         MultipartBody.Part body = MultipartBody.Part.createFormData("banner", file.getName(), reqFile);         RequestBody name = RequestBody.create(MediaType.parse("text/plain"), "banner");          Log.d("THIS", data.getData().getPath());          retrofit2.Call<okhttp3.ResponseBody> req = service.postImage(body, name);         req.enqueue(new Callback<ResponseBody>() {             @Override             public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {              }              @Override             public void onFailure(Call<ResponseBody> call, Throwable t) {                 Log.e("FFF", t.getMessage());                 t.printStackTrace();             }         });     } } 

Error stack:

Process: com.projectbox.uploadfile, PID: 17822 java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=100, result=-1, data=Intent { dat=content://com.android.providers.media.documents/document/image:28362 flg=0x1 }} to activity {com.projectbox.uploadfile/com.projectbox.uploadfile.MainActivity}: java.lang.NullPointerException at android.app.ActivityThread.deliverResults(ActivityThread.java:4220) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4263) at android.app.ActivityThread.-wrap20(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1601) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6349) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783) Caused by: java.lang.NullPointerException at java.io.File.<init>(File.java:262) at com.projectbox.uploadfile.MainActivity.onActivityResult(MainActivity.java:110) at android.app.Activity.dispatchActivityResult(Activity.java:7025) at android.app.ActivityThread.deliverResults(ActivityThread.java:4216) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4263)  at android.app.ActivityThread.-wrap20(ActivityThread.java)                                                                 

5 Answers

Answers 1

You can do it something like this.

if (requestCode == 3 && resultCode == RESULT_OK && data != null && data.getData() != null) {              Uri uri = data.getData();              try {                 String[] filePathColumn = {MediaStore.Images.Media.DATA};                 Cursor cursor = getContentResolver().query(uri, filePathColumn, null, null, null);                 cursor.moveToFirst();                 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);                 //  picturePath = cursor.getString(columnIndex);                  // try to copy image 1                 File destination = new File(Environment.getExternalStorageDirectory(),                         System.currentTimeMillis() + ".jpg");                 picturePath = "" + destination;                 cursor.close();                 img_member.setTag(picturePath);                 Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);                  ByteArrayOutputStream bytes = new ByteArrayOutputStream();                 bitmap.compress(Bitmap.CompressFormat.JPEG, 90, bytes);                 FileOutputStream fo;                 try {                     destination.createNewFile();                     fo = new FileOutputStream(destination);                     fo.write(bytes.toByteArray());                     fo.close();                 } catch (FileNotFoundException e) {                     e.printStackTrace();                 } catch (IOException e) {                     e.printStackTrace();                 }                 img_member.setImageBitmap(bitmap);                 mBottomSheetDialog.hide();             } catch (IOException e) {                 e.printStackTrace();             }          } 

Answers 2

It seems that the filepath is null. You are trying use the mediastore DATA column to get a File from a content:// URI. This may or may not work as explained by Mark Murphy in How to Consume Content From a Uri.

The only reliable way to get the content is to use ContentResolver.openInputStream()

You can then either copy the stream to a file before uploading it or directly upload the stream as explained by this OkHttp recipe.

Answers 3

You are getting wrong image path or Relative Image Path here. I Think that must Be the problem..

Try Replacing

        String[] filePathColumn = {MediaStore.Images.Media.DATA};         android.database.Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);         if (cursor == null)             return;          cursor.moveToFirst();          int columnIndex = cursor.getColumnIndex(filePathColumn[0]);         String filePath = cursor.getString(columnIndex);         cursor.close(); 

With

Cursor cursor = null; Uri contentUri = data.getData(); String filepath = "";         try {             String[] proj = {MediaStore.Images.Media.DATA};             cursor = context.getContentResolver().query(contentUri, proj, null, null, null);             int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);             cursor.moveToFirst();             filePath = cursor.getString(column_index);         } finally {             if (cursor != null) {                 cursor.close();             }         } 

Hope this will Solve your problem.

Answers 4

Try enabling keep activities from developer options ...that may be one of the cause I have faced earlier

Answers 5

if(!cursor.moveToFirst()) or if(TextUtils.isEmpty(filePath)) you should return from method:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {     super.onActivityResult(requestCode, resultCode, data);     if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {         android.net.Uri selectedImage = data.getData();         String[] filePathColumn = {MediaStore.Images.Media.DATA};         android.database.Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);         if (cursor == null)             return;          if(!cursor.moveToFirst()){             cursor.close();             return;          }            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);         String filePath = cursor.getString(columnIndex);         cursor.close();         if(TextUtils.isEmpty(filePath))           return;          File file = new File(filePath);          RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);         MultipartBody.Part body = MultipartBody.Part.createFormData("banner", file.getName(), reqFile);         RequestBody name = RequestBody.create(MediaType.parse("text/plain"), "banner");          Log.d("THIS", data.getData().getPath());          retrofit2.Call<okhttp3.ResponseBody> req = service.postImage(body, name);         req.enqueue(new Callback<ResponseBody>() {             @Override             public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {              }              @Override             public void onFailure(Call<ResponseBody> call, Throwable t) {                 Log.e("FFF", t.getMessage());                 t.printStackTrace();             }         });     } } 
Read More

Wednesday, April 25, 2018

mocking Retrofit response calls with Call not working

Leave a Comment

I'm mocking the response of the APIService. Unfortunately it is not working, I have to send back a Call but I don't understand how. The question is how to send back a Call object.

@RunWith(AndroidJUnit4::class) class ApiServiceTest {      @Test     fun testSomething() {         val apiService = ApiServiceMock()         val call = apiService.getStep1User()         val result = call.execute()         Assert.assertEquals("SomeUserValue", result.body()!!.getResponse())     } } 

Here is the mocked service:

class ApiServiceMock : ApiService {     override fun getStep1User(): Call<UserResponse> {         // How to return an object of type Call<UserResponse> ?         val response = "{ \"Response\": \"SomeUserValue\" }"         val gson = Gson().toJson(response)         return Response.success(gson)     } } 

Here is the api interface:

interface ApiService {      @GET("/booky/step1user")     fun getStep1User(): Call<UserResponse>      companion object {          val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)         val client = OkHttpClient.Builder()                 .addInterceptor(interceptor)                 .build()          val retrofit = Retrofit.Builder()                 .baseUrl("http://jimclermonts.nl")                 .addConverterFactory(MoshiConverterFactory.create().asLenient())                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                 .client(client)                 .build()     } } 

build.gradle:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-moshi:2.3.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0' implementation 'com.squareup.okhttp3:okhttp:3.9.0' implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"  implementation 'com.google.code.gson:gson:2.8.0'  testImplementation "org.mockito:mockito-core:2.12.0" testImplementation "com.nhaarman:mockito-kotlin:1.5.0" implementation 'org.mockito:mockito-android:2.18.0' 

3 Answers

Answers 1

Call is an interface, you can create an object that implements it and return it from your mocking method:

class ApiServiceMock : ApiService {     override fun getStep1User(): Call<UserResponse> {         return object: Call<SignedUserUi> {             override fun enqueue(callback: Callback<UserResponse>?) {             }              override fun isExecuted(): Boolean {                 return false             }              override fun clone(): Call<UserResponse> {                 return this             }              override fun isCanceled(): Boolean {                 return false             }              override fun cancel() {              }              override fun request(): Request {                 return Request.Builder().build()             }              override fun execute(): Response<UserResponse> {                 // Create your mock data in here                 val response = "{ \"Response\": \"SomeUserValue\" }"                 val gson = Gson().toJson(response)                 return Response.success(UserResponse(gson))             }          }     } } 

If you want to have less boilerplate and be able to mock interfaces in a single line I would recommend you to take a look at mockito for kotlin.

After including it to your project you'll be able to do

val rawResponse = "{ \"Response\": \"SomeUserValue\" }" val gson = Gson().toJson(rawResponse) val response = Response.success(UserResponse(gson))  val mockCall = mock<Call<UserResponse>> {     on { execute() } doReturn response }  val mockApiService = mock<ApiService> {     on { getStep1User() } doReturn mockCall } 

Answers 2

What you are trying to do is testin retrofit ! You must assert on the behavior of your application after getting the response, not asserting on what retrofit get as response of the request!! For example assert that an error response, an error dialog must appear.

You can mock the response using OkHTTPMock server. add the dependency in your build.gradle module file:

testImplementation 'com.squareup.okhttp3:mockwebserver:lastVersion' 

and then in your test file you can mock a server, a request and a response. here an example:

MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setBody("{ \"Response\": \"SomeUserValue\" }")); // Start the server.   server.start();  //and than load your request. Be Careful, they are executed in the order that you enqued them!  //Add your assertion (Succes response and error response) //if you are working with MVP architecture, you can assert for the success case //that method showData(data) is called using Mockito. verify(myPresenter).showData(data); 

Take a look at the officiel example of OkHttpMock

Answers 3

You need to use a helper class to mock responses.

class CallFake<T>(     private val response: Response<T>)  : Call<T> {  companion object {     inline fun <reified T> buildSuccess(body: T): CallFake<T> {         return CallFake(Response.success(body))     }      inline fun <reified T> buildHttpError(errorCode: Int, contentType: String, content: String): CallFake<T> {         return CallFake(Response.error(errorCode, ResponseBody.create(MediaType.parse(contentType), content)))     } }  override fun execute(): Response<T> = response  override fun enqueue(callback: Callback<T>?) {}  override fun isExecuted(): Boolean = false  override fun clone(): Call<T> = this  override fun isCanceled(): Boolean = false  override fun cancel() {}  override fun request(): Request? = null  } 

And then, in your test method you need to call it with when function.

`when`(userApi.getUsers(any()))             .thenReturn(CallFake.buildSuccess(buildUserListResponse())) 
Read More

Tuesday, November 14, 2017

Incompatible types inferred type does not conform to equality constraint(s)

Leave a Comment

So I have a model Model.

public class Model { .... }  

This has two subclasses:

public class SubmodelA extend Model { .... } 

and

public class SubmodelB extend Model { .... } 

These three are wrapped under Data class.

public class ApiData<T extends Model> {      public T data;  } 

My general response wrapper looks like this:

public class ApiResponse<DATA> {      DATA data; } 

The "dummy" api operation remains the same:

public interface Endpoints {      Call<ApiResponse<ApiData>> getData(); } 

I have an implementation of retrofit2.Callback to handle the responses:

public class ApiCallbackProxy<T> implements retrofit2.Callback<T> {      public interface ApiResultListener<RESPONSE_TYPE> {         void onResult(RESPONSE_TYPE response, ApiError error);     }      private ApiResultListener<T> mListener;      private ApiCallbackProxy(ApiResultListener<T> listener) {         mListener = listener;     }      @Override     public void onResponse(Call<T> call, Response<T> response) {      }      @Override     public void onFailure(Call<T> call, Throwable t) {      }      public static <T> ApiCallbackProxy<T> with(ApiResultListener<T> callback) {         return new ApiCallbackProxy<>(callback);     } } 

The ApiClient

public class ApiClient {      public Endpoints mRetrofit;      public ApiClient() {        Retrofit retrofit = new Retrofit.Builder().build();        mRetrofit = retrofit.create(Endpoints.class);     }      public <U extends Model> void getData(ApiResultListener<ApiResponse<ApiData<U>>> callback) {        //Compiler hits here        mRetrofit.getData().enqueue(ApiCallbackProxy.with(callback));     } } 

Compiler hits at ApiCallbackProxy.with(callback) with this error:

enter image description here

So I want depending on where this API call is used in the app to return a different subclass of model or the model itself.

ie.

public static void main (String[] args) {     ApiClient apiClient = new ApiClient();     apiClient.getData(listener2); }   public static final ApiResultListener<ApiResponse<Data<SubmodelA>>> listener = (response, error) -> {};  public static final ApiResultListener<ApiResponse<Data<Model>>> listener2 = (response, error) -> {};  public static final ApiResultListener<ApiResponse<Data<SubmodelB>>> listener3 = (response, error) -> {}; 

1 Answers

Answers 1

The ApiClient class has a listener that expects ApiData<U> in the response.

The problem is, there is no U. You have an Endpoint, and the endpoint has no generic types, and it returns just ApiData with no concrete type selected for the generic.

This is a case of generics gone wrong. The usual idea would be to make Endpoint generic:

public interface Endpoints<U> {     Call<ApiResponse<ApiData<U>>> getData(); } 

However, what does the Retrofit do? It turns HTTP API into Java interface. And looking at the most simple example at the github repo of Retrofit, it seems pretty clear to me that you are supposed to put interfaces that access some real HTTP endpoint. It is not some abstract GET.

So you'd rather give it a concrete type than make it generic. Do something like:

public interface Endpoints {     Call<ApiResponse<ApiData<Model>>> getData(); } 

I expect Retrofit to deserialize the data in the response to your Model. So, having a concrete class rather than an unset generic type variable is crucial to the successful deserialization. However, you can only use it with listeners being either of the following:

ApiResultListener<ApiResponse<Data<Model>>> ApiResultListener<ApiResponse<Data<? super Model>>> 

Also, in the older part of the question, the ApiResponse<Payload> part where the generic type variable looks like it was Payload class, now that is quite devious :)

Read More

Sunday, March 26, 2017

Retrofit2 Handle condition when status code 200 but json structure different than datamodel class

Leave a Comment

I'm using Retrofit 2 and RxJava2CallAdapterFactory.

The API I consume returns status code always as 200 and for success and response json string the json structure is entirely different. Since the status code is always 200 the onResponse() method is called always. Hence, I'm not able to extract error msgs from the json in the error condition.

Solution 1:

I use ScalarsConverterFactory to get response String and manually use Gson to parse the response . How to get response as String using retrofit without using GSON or any other library in android

Problem with this solution: I'm planning to use RxJava2CallAdapterFactory for that the retrofit method should return DataModel Class.

I need to find the best solution for this problem , in way I can keep returning the data model classes from Retrofit method & somehow I identify the error condition from response (identify the response json does not match the data model)& then parse the error json into a data model.

Retrofit Client

 public static Retrofit getClient(String url) {         if (apiClient == null) {              HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();              interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);             OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor).build();             apiClient = new Retrofit.Builder()                     .baseUrl(url)                     /*addCallAdapterFactory for RX Recyclerviews*/                     .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                     /* add ScalarsConverterFactory to get json string as response */ //                    .addConverterFactory(ScalarsConverterFactory.create())                     .addConverterFactory(GsonConverterFactory.create()) //                    .addConverterFactory(GsonConverterFactory.create(gson))                     .client(httpClient)                     .build();         }         return apiClient;     } 

Method

public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {         baseUrl = AppPreference.getParam(UiUtils.getContext(), SPConstants.BASE_URL, "").toString();         ApiInterface apiService =                 ApiClient.getClient(baseUrl).create(ApiInterface.class);          Call<LoginBean> call = apiService.getLoginResponse(queryParams);         call.enqueue(new Callback<LoginBean>() {              @Override             public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {                  if (response.body().isObjectNull()) {                     httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,                             HttpCallback.RETURN_TYPE_FAILURE, 0, null);                     return;                 }                 httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,                         HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body());             }              @Override         public void onFailure(Call<LoginBean> call, Throwable t) {             // Log error here since request failed             httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,                     HttpCallback.RETURN_TYPE_FAILURE, 0, t);             t.printStackTrace();         }     }); } 

Interface

@GET("App/login") Call<LoginBean> getLoginResponse(@QueryMap Map<String, String> queryMap); 

PS : The API cannot change for now, as some other applications are consuming it.

Gson parser does not return a null object instance for me to understand that there is json structure and datamodel mismatch.

RestAdapter is depreciated in Retrofit 2

I'm looking for the best approach to resolve this , preferabbly avoid manually json parsing and take most advantage of retrofit and RX adapters.

EDIT

Response code 200 hence

  1. response.isSuccessful() ==true

  2. response.body() != null is also true as Gson never creates a null instance or throws any exception if there is mismatch of json structure

  3. response.errorBody()== null at all times as response sent as input stream from the server.

if (response.isSuccessful() && response.body() != null) {                 //control always here as status code 200 for error condition //also             }else if(response.errorBody()!=null){                 //control never reaches here             } 

5 Answers

Answers 1

So you have two different successful (status code 200) responses from the same endpoint. One being the actual data model and one being an error (both as a json structure like this?:

Valid LoginBean response:

{   "id": 1234,   "something": "something" } 

Error response

{   "error": "error message" } 

What you can do is have an entity that wraps both cases and use a custom deserializer.

class LoginBeanResponse {   @Nullable private final LoginBean loginBean;   @Nullable private final ErrorMessage errorMessage;    LoginBeanResponse(@Nullable LoginBean loginBean, @Nullable ErrorMessage errorMessage) {     this.loginBean = loginBean;     this.errorMessage = errorMessage;   }   // Add getters and whatever you need } 

A wrapper for the error:

class ErrorMessage {   String errorMessage;   // And whatever else you need   // ... } 

Then you need a JsonDeserializer:

public class LoginBeanResponseDeserializer implements JsonDeserializer<LoginBeanResponse> {    @Override   public LoginBeanResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {      // Based on the structure you check if the data is valid or not     // Example for the above defined structures:      // Get JsonObject     final JsonObject jsonObject = json.getAsJsonObject();     if (jsonObject.has("error") {       ErrorMessage errorMessage = new Gson().fromJson(jsonObject, ErrorMessage.class);       return new LoginBeanResponse(null, errorMessage)     } else {       LoginBean loginBean = new Gson().fromJson(jsonObject, LoginBean.class):       return new LoginBeanResponse(loginBean, null);     }   } } 

Then add this deserializer to the GsonConverterFactory:

GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(LoginBeanResponse.class, new LoginBeanResponseDeserializer()).create():  apiClient = new Retrofit.Builder()     .baseUrl(url)     .addCallAdapterFactory(RxJava2CallAdapterFactory.create())     .addConverterFactory(GsonConverterFactory.create(gsonBuilder))     .client(httpClient)     .build(); 

This is the only way I can think of making this work. But as already mentioned this kind of API design is just wrong because status codes are there for a reason. I still hope this helps.

EDIT: What you can then do inside the class where you make the call to that Retrofit (if you already converted from Call<LoginBeanResponse> to Single<LoginBeanResponse> with RxJava) is actually return a proper error. Something like:

Single<LoginBean> getLoginResponse(Map<String, String> queryMap) {     restApi.getLoginResponse(queryMap)         .map(loginBeanResponse -> { if(loginBeanResponse.isError()) {             Single.error(new Throwable(loginBeanResponse.getError().getErrorMessage()))         } else {              Single.just(loginBeanReponse.getLoginBean())          }}) } 

Answers 2

It seems out that Retrofit's use of Gson by default makes it easy to add a custom deserialize to handle the portion of the JSON document that was the problem.

Sample code

@FormUrlEncoded     @POST(GlobalVariables.LOGIN_URL)     void Login(@Field("email") String key, @Field("password") String value, Callback<Response> callback);  getService().Login(email, password, new MyCallback<Response>(context, true, null) {     @Override     public void failure(RetrofitError arg0)      {         // TODO Auto-generated method stub         UtilitySingleton.dismissDialog((BaseActivity<?>) context);         System.out.println(arg0.getResponse());       }      @Override     public void success(Response arg0, Response arg1)     {          String result = null;          StringBuilder sb = null;          InputStream is = null;          try          {                 is = arg1.getBody().in();                 BufferedReader reader = new BufferedReader(new InputStreamReader(is));                 sb = new StringBuilder();                 String line = null;                 while ((line = reader.readLine()) != null)                 {                     sb.append(line + "\n");                     result = sb.toString();                     System.out.println("Result :: " + result);                 }             }             catch (Exception e)             {                 e.printStackTrace();             }         }     }); 

Answers 3

You can simply do that by doing this

try { String error = response.errorBody().string(); error = error.replace("\"", ""); Toast.makeText(getContext(), error, Toast.LENGTH_LONG).show(); } catch (IOException e) { e.printStackTrace(); } 

Answers 4

One possible solution is to make Gson fail on unknown properties. There seems to be an issue raised already(https://github.com/google/gson/issues/188). You can use the workaround provided in the issue page. So the steps are as follows:

Add the workaround ValidatorAdapterFactory to the code base:

public class ValidatorAdapterFactory implements TypeAdapterFactory {  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {     // If the type adapter is a reflective type adapter, we want to modify the implementation using reflection. The     // trick is to replace the Map object used to lookup the property name. Instead of returning null if the     // property is not found, we throw a Json exception to terminate the deserialization.     TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);      // Check if the type adapter is a reflective, cause this solution only work for reflection.     if (delegate instanceof ReflectiveTypeAdapterFactory.Adapter) {          try {             // Get reference to the existing boundFields.             Field f = delegate.getClass().getDeclaredField("boundFields");             f.setAccessible(true);             Map boundFields = (Map) f.get(delegate);              // Then replace it with our implementation throwing exception if the value is null.             boundFields = new LinkedHashMap(boundFields) {                  @Override                 public Object get(Object key) {                      Object value = super.get(key);                     if (value == null) {                         throw new JsonParseException("invalid property name: " + key);                     }                     return value;                  }              };             // Finally, push our custom map back using reflection.             f.set(delegate, boundFields);          } catch (Exception e) {             // Should never happen if the implementation doesn't change.             throw new IllegalStateException(e);         }      }     return delegate;     }  } 

Build a Gson object with this TypeAdaptorFactory:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ValidatorAdapterFactory()).create() 

And then use this gson instance in GsonConverterFactory like below:

apiClient = new Retrofit.Builder()                 .baseUrl(url)                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                 .addConverterFactory(GsonConverterFactory.create(gson)) //Made change here                 .client(httpClient)                 .build(); 

This should throw an error if the unmarshalling step finds an unknown property, in this case the error response structure.

Answers 5

Here is another attempt. General idea: create a custom Converter.Factory based on GsonConverterFactory and a custom Converter<ResponseBody, T> converter based on GsonRequestBodyConverter to parse whole body 2 times: first time as error and second time as actual expected response type. In this way we can parse error in a single place and still preserve friendly external API. This is actually similar to @anstaendig answer but with much less boilerplate: no need for additional wrapper bean class for each response and other similar stuff.

First class ServerError that is a model for your "error JSON" and custom exception ServerErrorException so you can get all the details

public class ServerError {      // add here actual format of your error JSON     public String errorMsg; }  public class ServerErrorException extends RuntimeException {     private final ServerError serverError;      public ServerErrorException(ServerError serverError)     {         super(serverError.errorMsg);         this.serverError = serverError;     }      public ServerError getServerError()     {         return serverError;     } } 

Obviously you should change the ServerError class to match your actual data format.

And here is the main class GsonBodyWithErrorConverterFactory:

public class GsonBodyWithErrorConverterFactory extends Converter.Factory {     private final Gson gson;     private final GsonConverterFactory delegate;     private final TypeAdapter<ServerError> errorTypeAdapter;       public GsonBodyWithErrorConverterFactory()     {         this.gson = new Gson();         this.delegate = GsonConverterFactory.create(gson);         this.errorTypeAdapter = gson.getAdapter(TypeToken.get(ServerError.class));     }      @Override     public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)     {         return new GsonBodyWithErrorConverter<>(gson.getAdapter(TypeToken.get(type)));     }      @Override     public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit)     {         return delegate.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);     }      @Override     public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit)     {         return delegate.stringConverter(type, annotations, retrofit);     }       class GsonBodyWithErrorConverter<T> implements Converter<ResponseBody, T>     {         private final TypeAdapter<T> adapter;          GsonBodyWithErrorConverter(TypeAdapter<T> adapter)         {             this.adapter = adapter;         }          @Override         public T convert(ResponseBody value) throws IOException         {             // buffer whole response so we can safely read it twice             String contents = value.string();              try             {                 // first parse response as an error                 ServerError serverError = null;                 try                 {                     JsonReader jsonErrorReader = gson.newJsonReader(new StringReader(contents));                     serverError = errorTypeAdapter.read(jsonErrorReader);                 }                 catch (Exception e)                 {                     // ignore and try to read as actually required type                 }                 // checked that error object was parsed and contains some data                 if ((serverError != null) && (serverError.errorMsg != null))                     throw new ServerErrorException(serverError);                  JsonReader jsonReader = gson.newJsonReader(new StringReader(contents));                 return adapter.read(jsonReader);             }             finally             {                 value.close();             }         }     } } 

The basic idea is that the factory delegates other calls to the standard GsonConverterFactory but intercepts responseBodyConverter to create a custom GsonBodyWithErrorConverter. The GsonBodyWithErrorConverter is doing the main trick:

  1. First it reads whole response as String. This is required to ensure response body is buffered so we can safely re-read it 2 times. If your response actually might contain some binary you should read and buffer the response as binary and unfortunately retrofit2.Utils.buffer is not a public method but you can create a similar one yourself. I just read the body as a String as it should work in simple cases.
  2. Create a jsonErrorReader from the buffered body and try to read the body as a ServerError. If we can do it, we've got an error so throw our custom ServerErrorException. If we can't read it in that format - just ignore exception as it is probably just normal successful response
  3. Actually try to read the buffered body (second time) as the requested type and return it.

Note that if your actual error format is not JSON you still can do all the same stuff. You just need to change the error parsing logic inside GsonBodyWithErrorConverter.convert to anything custom you need.

So now in your code you can use it as following

.addConverterFactory(new GsonBodyWithErrorConverterFactory()) // use custom factory //.addConverterFactory(GsonConverterFactory.create()) //old, remove 

Note: I haven't actually tried this code so there might be bugs but I hope you get the idea.

Read More

Friday, January 20, 2017

How to make custom implementation of Retrofit2.Call<T>

Leave a Comment

I am using Retrofit2 and I want to Override its Call.enqueue method.

I did this so far:

Custom Call:

    public class CustomCall<T> implements Call<T> {          private final Call<T> delegate;         //..every method has delegate method invoked in it 

Apis:

        @GET         CustomCall<TKBaseResponse> testConnection(@Url String customUrl); 

But I keep getting these errors:

    Unable to create call adapter for CustomCall<....> 

and

    Could not locate call adapter for CustomCall<....> 

Any way on how can I do this properly? Thanks in advance!

2 Answers

Answers 1

First create a ServiceManager class -

public final class ServiceManager {      private static ServiceManager sServiceManager;      /**      * Gets the instance of the web services implementation.      *      * @return the singleton instance.      */     public static ServiceManager get() {         if (sServiceManager == null) {             sServiceManager = new ServiceManager();         }         return sServiceManager;     }      /**      * Creates the services for a given HTTP Url, useful when testing      * through multiple endpoints and unit testing      *      * @param clazz the service class.      * @param <T>   type of the service.      * @return the created services implementation.      */     public <T> T createService(Class<T> clazz) {         return createService(clazz, HttpUrl.parse(ServiceApiEndpoints.SERVICE_ENDPOINT));     }      /**      * Creates the services for a given HTTP Url, useful when testing      * through multiple endpoints and unit testing      *      * @param clazz   the service class.      * @param httpUrl the endpoint      * @param <T>     type of the service.      * @return the created services implementation.      */     public <T> T createService(Class<T> clazz, HttpUrl httpUrl) {         Retrofit retrofit = getRetrofit(httpUrl);         return retrofit.create(clazz);     }      public <T> T createService(Class<T> clazz, Retrofit retrofit) {         return retrofit.create(clazz);     }      private Retrofit getRetrofit(HttpUrl httpUrl) {         return new Retrofit.Builder()                 .baseUrl(httpUrl)                 .client(createClient())                 .addConverterFactory(getConverter())                 .build();     }      public Retrofit getPlainRetrofit(HttpUrl httpUrl) {         return new Retrofit.Builder()                 .baseUrl(httpUrl)                 .client(new OkHttpClient.Builder().build())                 .addConverterFactory(getConverter())                 .build();     }      private Converter.Factory getConverter() {         return GsonConverterFactory.create();     }       private OkHttpClient createClient() {         return new OkHttpClient.Builder().addInterceptor(new RequestInterceptor()).build();     }  } 

ServiceApiEndpoints is a class contains service endpoints.

final class ServiceApiEndpoints {      public static final String SERVICE_ENDPOINT = "your_app_url"; } 

Create an interface APIService

public interface APIService {  String GET_INFO = "get_info";      @GET(GET_INFO)     Call<ResInfo[]> getInfo(); } 

Create ResInfo model.

public class ResInfo {     private static final String FIELD_CONTENT = "content";      public String getContent() {         return mContent;     }      public void setContent(final String content) {         mContent = content;     }       @SerializedName(FIELD_CONTENT)     private String mContent;      public ResInfo(){      } } 

Call the request.

    private Call<ResInfo[]> mGetInfoAPICall;      APIService apiService=ServiceManager.get().createService(APIService.class);     mGetInfoAPICall = apiService.getInfo();     mGetInfoAPICall.enqueue(new Callback<ResInfo[]>() {     @Override     public void onResponse(Call<ResInfo[]> call, Response<ResInfo[]> response) {      }      @Override     public void onFailure(Call<ResInfo[]> call, Throwable t) {      } }); 

Answers 2

I am posting below an example to help you with retrofit implementation.

Create your resource like this (MyResource.java).

Call<TKBaseResponse> testConnection(@@Url String customUrl); 

Initialize Retrofit

private Resource getRetrofitResource(){    //Initialize retrofit.    final Retrofit = .....//your code to initialize retrofit    return retrofit.create(MyResource.class); } 

To implement call enqueue(async retrofit calls) you need to pass your resource response and a response handler which is your custom implementation into the enqueue method. I am posting my implementation of ResponseHandler alongside.

public abstract class ResponseHandler<T> {      private static final String TAG = ResponseHandler.class.getSimpleName();      private static final String LINE_SEPARATOR = System.getProperty("line.separator");      private final Context context;      public ResponseHandler() {         this(null);     }      public ResponseHandler(final Context context) {         this.context = context;     }       public abstract void onResponse(final T response);      public void onError(final ErrorResponse errorResponse) {         if (context == null) {             return;         }         Log.e(TAG, "An error occurred while invoking service. Error Code: " + errorResponse.getErrorCode() + LINE_SEPARATOR + "Message: " + errorResponse.getMessage() + LINE_SEPARATOR);         final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);         alertBuilder.setCancelable(true);         alertBuilder.setTitle(R.string.title_server_error_dialog);         alertBuilder.setMessage(R.string.network_error_message);         alertBuilder.setPositiveButton(R.string.text_ok, new DialogInterface.OnClickListener() {             @Override             public void onClick(final DialogInterface dialog, final int which) {                 dialog.dismiss();             }         });         alertBuilder.show();     }      public void onFailure(Throwable throwable) {         if (context == null) {             return;         }         Log.e(TAG, "An error occurred while invoking service", throwable);         final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);         alertBuilder.setCancelable(true);         alertBuilder.setTitle(R.string.title_network_error_dialog);         alertBuilder.setMessage(R.string.network_error_message);         alertBuilder.setPositiveButton(R.string.text_ok, new DialogInterface.OnClickListener() {             @Override             public void onClick(final DialogInterface dialog, final int which) {                 dialog.dismiss();             }         });         alertBuilder.show();     }  } 

Create a method handle response.

protected <T> void handleResponse(Call<T> call, final ResponseHandler<T> responseHandler) {         call.enqueue(new Callback<T>() {             @Override             public void onResponse(final Call<T> call, final Response<T> response) {                if (response.isSuccess()) {                 if (responseHandler != null ) {                     responseHandler.onResponse(response.body());                 }             } else {                 final ErrorResponse errorResponse = parseError(response);                 if (responseHandler != null) {                     responseHandler.onError(errorResponse);                 }             }             }              @Override             public void onFailure(final Call<T> call, final Throwable throwable) {                if (responseHandler != null) {                 responseHandler.onFailure(throwable);             }             }         });     } 

Please let me know if you have any doubts around this.

Now from call your resource like below.

final MyResource resource = getRetrofitResource(); final Call<TKBaseResponse> response = resource .testConnection("ANY_URL_OF_YOUR_CHOICE"); handleResponse(response, new ResponseHandler<TKBaseResponse>(){   public void onResponse(final TKBaseResponse response){       //Do whatever you want to do here..  }     }); 
Read More

Sunday, July 24, 2016

Why async methods call sooner in Retrofit 2?

Leave a Comment

As you can see in the code below, I've call the getPostByPageId() method to get data from server and then check if the data was returned back, I do other jobs.

 private void recyclerViewJobs() {     getPostByPageId();     if (pageDtoList.size() > 0) {         emptyText.setVisibility(View.GONE);         recyclerView.setVisibility(View.VISIBLE);         PagesAdapter adapter = new PagesAdapter(pageDtoList, context);         recyclerView.setAdapter(adapter);         recyclerView.setLayoutManager(new LinearLayoutManager(context));     } else {         emptyText.setVisibility(View.VISIBLE);         recyclerView.setVisibility(View.GONE);     }   } 

.....

private void getPostByPageId() {      IPageEndPoint pageEndPoint = ServiceGenerator.createService(IPageEndPoint.class);     Call<List<PageDto>> call = pageEndPoint.getPostByPageId(profileId);     call.enqueue(new retrofit2.Callback<List<PageDto>>() {         @Override         public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {             if (response.isSuccessful()) {                 pageDtoList = response.body();             } else {                 log.toast("response is not successful for getPostByPageId");             }         }          @Override         public void onFailure(Call<List<PageDto>> call, Throwable t) {             log.toast("getPostByPageId onFailure");         }     }); } 

I don't know why in the recyclerViewJobs() method, the if condition work first?

maybe I could not explain my issue very well but my big problem is to know when I want to get some data from REST and then use this data to another REST service, how can I do this job because enqueue in retrofit works asynchronous and do not wait for first method, because of that it is beginning the second method in another thread and because my data was not gotten from first method yet, my program was crashed and I didn't get the answer. That is my problem.....

Cheers

3 Answers

Answers 1

That's because retrofit's enqueue method is not a blocking method. which means the Thread calling it won't wait for it to finish its job. Like @Amir said, If you have other things to do after calling getPostByPageId(), you should put them after retrofit's callback:

    private void recyclerViewJobs() {         getPostByPageId();     }     private void getPostByPageId() {          IPageEndPoint pageEndPoint = ServiceGenerator.createService(IPageEndPoint.class);         Call<List<PageDto>> call = pageEndPoint.getPostByPageId(profileId);         call.enqueue(new retrofit2.Callback<List<PageDto>>() {             @Override             public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {                 if (response.isSuccessful()) {                     pageDtoList = response.body();                     if (pageDtoList.size() > 0) {                         emptyText.setVisibility(View.GONE);                         recyclerView.setVisibility(View.VISIBLE);                         PagesAdapter adapter = new PagesAdapter(pageDtoList, context);                         recyclerView.setAdapter(adapter);                         recyclerView.setLayoutManager(new LinearLayoutManager(context));                     } else {                         emptyText.setVisibility(View.VISIBLE);                         recyclerView.setVisibility(View.GONE);                     }                 } else {                     log.toast("response is not successful for getPostByPageId");                 }             }              @Override             public void onFailure(Call<List<PageDto>> call, Throwable t) {                 log.toast("getPostByPageId onFailure");             }         });     } 

Answers 2

Your async call run first But your if-condition always goes false because if-condition not behave as you expected! As soon as your async request sent it goes to check if-condition and because your list is empty (your response from server is not fetch yet) it always return false.

If you want to call next operation just after your WebService response was successful you can do it with following code:

public void onResponse(Call<List<PageDto>> call, Response<List<PageDto>> response) {      if (response.body().YOUR_LIST.size() > 0) {         emptyText.setVisibility(View.GONE);         recyclerView.setVisibility(View.VISIBLE);         PagesAdapter adapter = new PagesAdapter(pageDtoList, context);         recyclerView.setAdapter(adapter);         recyclerView.setLayoutManager(new LinearLayoutManager(context));          //CALL_YOUR_NEXT webservice here     } else {         emptyText.setVisibility(View.VISIBLE);         recyclerView.setVisibility(View.GONE);     } } 

In your processResponse do your job. But one thing you should consider is that response.isSuccessful() return true most of time and it doesn't mean your responseBody contains data. according to document:

isSuccessful() Returns true if code() is in the range [200..300).

So the better way is to check that your response list contains data or not.

But as a better way (a bit difficult if you are beginner) is using RxJava/RxAndroid. you can process your call just one after other with flatMap.

Answers 3

What is an asynchronous task? Not only for retrofit but the answer is same for all, when you use asynchronous task to write some code, it seems you allow that code to run asynchronously, that is, any time it wants to run and that too in background without disturbing any other tasks and not giving any kind of effect on main UI thread. So asynchronous task clearly defines itself as a task running in background and the code written above or below is never affected by it. So in your case it asynchronous task might not have completed or may be it is possible that it might not had started also but it does not disturb your if condition. https://developer.android.com/reference/android/os/AsyncTask.html

Read More

Sunday, May 1, 2016

Twitter OAuth Rest Api Status Parameter '@' symbol

Leave a Comment

I am using twitter rest api which is (https://api.twitter.com/1.1/).

First of all I used signpost library to generate oauth_signature. it is working well.

Upload Status endpoint (https://api.twitter.com/1.1/statuses/upload.json) is working well, but if status parameter contains '@' symbol, that is not working. So here is my code

TwitterStatusesService.java

import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.http.Query;  public interface TwitterStatusesService {     @POST("/1.1/statuses/update.json")     Call<ResponseBody> update(@Query("status") String status, @Query("in_reply_to_status_id") String inReplyToStatusId, @Query("lat") Double lat, @Query("long") Double lon, @Query("media_ids") String mediaIds); } 

TwitterStatusesAPIClient.java

import android.util.Log;  import com.twitter.sdk.android.core.TwitterAuthToken; import com.twitter.sdk.android.core.TwitterCore; import com.twitter.sdk.android.core.TwitterSession;  import okhttp3.OkHttpClient; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer; import se.akerfeldt.okhttp.signpost.SigningInterceptor;  public class TwitterStatusesClient {      private final String TAG = getClass().getSimpleName();      private static final String BASE_URL = "https://api.twitter.com/";      private final TwitterStatusesService apiService;      private static TwitterStatusesClient webServiceClient;      public static TwitterStatusesClient getInstance() {         if (webServiceClient == null)             webServiceClient = new TwitterStatusesClient();         return webServiceClient;     }      private TwitterStatusesClient() {         private TwitterStatusesClient() {         OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(TWITTER_KEY, TWITTER_SECRET);          TwitterSession activeSession = TwitterCore.getInstance().getSessionManager().getActiveSession();         if (activeSession != null) {             TwitterAuthToken authToken = activeSession.getAuthToken();             String token = authToken.token;             String secret = authToken.secret;             consumer.setTokenWithSecret(token, secret);         }          OkHttpClient client = new OkHttpClient.Builder()                 .addInterceptor(new SigningInterceptor(consumer))                 .build();          Retrofit retrofit = new Retrofit.Builder()                 .baseUrl(BASE_URL)                 .client(client)                 .build();          apiService = retrofit.create(TwitterStatusesService.class);     }      public Call<ResponseBody> update(String status, String statusId, Double lat, Double lon, String mediaIds) {         return apiService.update(status, statusId, lat, lon, mediaIds);     } } 

calling api client

String status = "@example"; TwitterStatusesClient.getInstance().update(status, null, null, null, null).enqueue(new Callback<ResponseBody>() {         @Override         public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {             Log.v(TAG, "onResponse");             progressDialog.dismiss();             try {                 if (response.errorBody() != null) {                     String error = response.errorBody().string();                     Log.e(TAG, "Error : " + error);                     ToastUtils.showErrorMessage(getContext(), "Error : " + error);                     return;                 }                  String body = response.body().string();                 Log.v(TAG, "body : " + body);             } catch (Exception e) {                 e.printStackTrace();             }         }          @Override         public void onFailure(Call<ResponseBody> call, Throwable t) {             Log.v(TAG, "onFailure");             t.printStackTrace();         }     }); 

giving error :

{"errors":[{"code":32,"message":"Could not authenticate you."}]} 

but, if I will use status variable "just example" instead of "@example" ( specific point is removing @ character ) that is working. only '@' symbol is not working.

EDIT

This is my manually creating OAuth v1.0a signature signing code via retrofit interceptor :

OkHttpClient client = new OkHttpClient.Builder()             .addInterceptor(new Interceptor() {                 @Override                 public Response intercept(Interceptor.Chain chain) throws IOException {                     Request request = chain.request();                      String method = request.method();                     String baseUrl = "https://api.twitter.com" + request.url().url().getPath();                      String oauthToken = "";                     String oauthTokenSecret = "";                      TwitterSession activeSession = TwitterCore.getInstance().getSessionManager().getActiveSession();                     if (activeSession != null) {                         TwitterAuthToken authToken = activeSession.getAuthToken();                         oauthToken = authToken.token;                         oauthTokenSecret = authToken.secret;                     }                      String oauthNonce = "TXZScw4M8TG";                     String oauthSignatureMethod = "HMAC-SHA1";                     String oauthTimestamp = String.valueOf(System.currentTimeMillis() / 1000);                     String oauthVersion = "1.0";                      String parameterString = "";                      parameterString = OAuthParams.addParam(request, parameterString, "count");                     parameterString = OAuthParams.addParam(request, parameterString, "id");                     parameterString = OAuthParams.addParam(request, parameterString, "in_reply_to_status_id");                      // if any parameter added to parameterString, append '&' character.                     if (parameterString.length() > 0) {                         parameterString += "&";                     }                      parameterString += "oauth_consumer_key=" + TWITTER_KEY + "&"                             + "oauth_nonce=" + oauthNonce + "&"                             + "oauth_signature_method=" + oauthSignatureMethod + "&"                             + "oauth_timestamp=" + oauthTimestamp + "&"                             + "oauth_token=" + oauthToken + "&"                             + "oauth_version=" + oauthVersion;                      // add status parameter to parameterString.                     parameterString = OAuthParams.addParam(request, parameterString, "status");                      Log.d(TAG, "normalizedParameters : " + parameterString);                     Log.d(TAG, "parameterStringPercent : " + OAuth.percentEncode(parameterString));                      String signatureBaseString = "";                     signatureBaseString += OAuth.percentEncode(method) + "&";                     signatureBaseString += OAuth.percentEncode(baseUrl) + "&";                     signatureBaseString += OAuth.percentEncode(parameterString);                      String oauthSignature = OauthSignature.generateSignature(signatureBaseString, TWITTER_SECRET,                             oauthTokenSecret);                      String authorization = "OAuth oauth_consumer_key=\"" + TWITTER_KEY + "\", " +                             "oauth_signature_method=\"HMAC-SHA1\", " +                             "oauth_timestamp=\"" + oauthTimestamp + "\", " +                             "oauth_nonce=\"" + oauthNonce + "\", " +                             "oauth_version=\"1.0\", " +                             "oauth_token=\"" + oauthToken + "\", " +                             "oauth_signature=\"" + OAuth.percentEncode(oauthSignature) + "\"";                      Log.w(TAG, "Authorization : " + authorization);                      request = request.newBuilder()                             .addHeader("Authorization", authorization)                             .build();                     return chain.proceed(request);                 }             }).addInterceptor(interceptor).build(); 

OAuth.java

public static String percentEncode(String s) {     if (s == null) {         return "";     }     try {         return URLEncoder.encode(s, ENCODING)                 // OAuth encodes some characters differently:                 .replace("+", "%20").replace("*", "%2A")                 .replace("%7E", "~");         // This could be done faster with more hand-crafted code.     } catch (UnsupportedEncodingException wow) {         throw new RuntimeException(wow.getMessage(), wow);     } } 

OAuthSignature.java

import android.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec;  public class OauthSignature {  public static String generateSignature(String signatueBaseStr, String oAuthConsumerSecret, String oAuthTokenSecret) {     byte[] byteHMAC = null;     try {         Mac mac = Mac.getInstance("HmacSHA1");         SecretKeySpec spec;         if (null == oAuthTokenSecret) {             String signingKey = OAuth.percentEncode(oAuthConsumerSecret) + '&';             spec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");         } else {             String signingKey = OAuth.percentEncode(oAuthConsumerSecret) + '&' + OAuth.percentEncode(oAuthTokenSecret);             spec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1");         }         mac.init(spec);         byteHMAC = mac.doFinal(signatueBaseStr.getBytes());     } catch (Exception e) {         e.printStackTrace();     }     return new String(Base64.encode(byteHMAC, Base64.DEFAULT)); } } 

1 Answers

Answers 1

I suggest go with fabric - https://fabric.io which has twitters various libs like twitter login, crashlytics and alss you need to setup fabric plugin with android studio. Post that you will be able to post/retrieve you tweets irrespective what you want to post.

For your question - check that '@' is special symbol where by retrofit is causing a problem. These kind of issues come up with retrofit which I have faced in different scenarios.

Try using Fabric for Twitter Login/oAuth

Read More

Tuesday, April 19, 2016

RxJava/Retrofit - How can I force users to use a specific subclass of Subscriber?

Leave a Comment

Before using rx.Observable, I used a custom callback with retrofit so I can add some specific logic for handling response/error and not have to do that inside the callback for every request as boilerplate code.

I force users to use the custom callback by putting it in the method signature like this:

@GET("/user_endpoint/") void getUser(CustomCallback<User> callback);  @GET("/profile_endpoint/") void getProfile(CustomCallback<Profile> callback); 

but now that I'm returning an Observable:

@GET("/user_endpoint/") Observable<User> getUser();  @GET("/profile_endpoint/") Observable<Profile> getProfile(); 

I can't figure out a way to make sure that a custom callback always proxies the error/response.

Also, with retrofit2.0, how can I force the user to use a custom callback with the returned Call object?

CustomCallback for reference:

public abstract class CustomCallback<T> implements Callback<T> {    @Override public final void success(T t, Response response) {     // do some logic     onSuccess(t);   }    @Override public final void failure(RetrofitError error) {     // do something with the error here such as show a Toast     Toast.makeText(Application.getInstance(), error.getLocalizedMessage(), Toast.LENGTH_SHORT).show();     onFailure(error);   }    public abstract void onSuccess(T response);    public abstract void onFailure(Throwable error); } 

1 Answers

Answers 1

Stop. You're thinking this the wrong way.

Instead consider this: You have the normal Retrofit interface:

interface Foo {    @GET("/user_endpoint/")    Observable<User> getUser(); } 

And then you have your decorator class:

public class FooDecorator implements Foo {     private Foo delegate = ...; // inject or create the Retrofit instance.      @Override     public Observable<User> getUser() {        return delegate.getUser().doOnNext(...).doOnError(...);     } } 

Then you use only the second class everywhere in your code (preferably just let the DI system use that) and you're set.

If you're feeling adventurous, you could even adapt the RxJavaCallAdapterFactory so that it modifies the returned observables without the need of a custom class.

Read More

Thursday, March 17, 2016

MockRetrofit doesn't allow to avoid network error

Leave a Comment

I using MockRetrofit(retrofit 2 snapshot 4) to mock the server apis.

NetworkBehavior networkBehavior = NetworkBehavior.create();           networkBehavior.setFailurePercent(1);          networkBehavior.setDelay(500, TimeUnit.MILLISECONDS);           MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit)                                                      .networkBehavior(networkBehavior)                                                      .build();           BehaviorDelegate<FinderCommonApis> delegate = mockRetrofit.create(FinderCommonApis.class);          finderCommonApis = new MockFinderCommonApis(delegate); 

I am also using this mock in my test cases. My test cases intermittently fail because of the NetworkBehavior. It introduces the network error which causes the intermittent failure. I want to completely avoid this network error, but it doesn't look like I can completely avoid it. I even tried to set the failure percentage to 0 but it doesn't allow. So I set the lowest possible value that is 1. They have made NetworkBehavior optional in MockRetrofit.Builder but it provides its own default NetworkBehavior which also introduces same network error. I checked the NetworkBehavior code and they have made it final class so I can't override it. I am running out of idea to avoid this problem.

I just wanted to know how to completely avoid the NetworkBehavior.

1 Answers

Answers 1

I even tried to set the failure percentage to 0 but it doesn't allow.

This is not true. See: https://github.com/square/retrofit/blob/78897005be619c3b63d238bf5d0de0f1580d95d4/retrofit-mock/src/main/java/retrofit2/mock/NetworkBehavior.java#L106-L109

0 is a valid value and is what should be used to disable all failure.

Read More

Friday, March 11, 2016

Android Unit Test with Retrofit2 and Mockito or Robolectric

Leave a Comment

Can I test real response from retrofit2beta4? Do i need Mockito or Robolectic?

I don't have activities in my project, it will be a library and I need to test is server responding correctly. Now I have such code and stuck...

@Mock ApiManager apiManager;  @Captor private ArgumentCaptor<ApiCallback<Void>> cb;  @Before public void setUp() throws Exception {     apiManager = ApiManager.getInstance();     MockitoAnnotations.initMocks(this); }  @Test public void test_login() {     Mockito.verify(apiManager)            .loginUser(Mockito.eq(login), Mockito.eq(pass), cb.capture());     // cb.getValue();     // assertEquals(cb.getValue().isError(), false); } 

I can make fake response, but I need to test real. Is it success? Is it's body correct? Can you help me with code?

2 Answers

Answers 1

It is generally not a good idea to test real server requests. See this blog post for an interesting discussion on the topic. According to the author, using your real server is a problem because:

  • Another moving piece that can intermittently fail
  • Requires some expertise outside of the Android domain to deploy the server and keep it updated
  • Difficult to trigger error/edge cases
  • Slow test execution (still making HTTP calls)

You can avoid all the issues above by using a mock server such as OkHttp's MockWebServer to simulate real response results. For example:

@Test public void test() throws IOException {     MockWebServer mockWebServer = new MockWebServer();      Retrofit retrofit = new Retrofit.Builder()             .baseUrl(mockWebServer.url("").toString())             //TODO Add your Retrofit parameters here             .build();      //Set a response for retrofit to handle. You can copy a sample     //response from your server to simulate a correct result or an error.     //MockResponse can also be customized with different parameters     //to match your test needs     mockWebServer.enqueue(new MockResponse().setBody("your json body"));      YourRetrofitService service = retrofit.create(YourRetrofitService.class);      //With your service created you can now call its method that should      //consume the MockResponse above. You can then use the desired     //assertion to check if the result is as expected. For example:     Call<YourObject> call = service.getYourObject();     assertTrue(call.execute() != null);      //Finish web server     mockWebServer.shutdown(); } 

If you need to simulate network delays, you can customize your response as follows:

MockResponse response = new MockResponse()     .addHeader("Content-Type", "application/json; charset=utf-8")     .addHeader("Cache-Control", "no-cache")     .setBody("{}"); response.throttleBody(1024, 1, TimeUnit.SECONDS); 

Alternatively, you can use MockRetrofit and NetworkBehavior to simulate API responses. See here an example of how to use it.

Finally, if you just want to test your Retrofit Service, the easiest would be to create a mock version of it that emits mock results for your tests. For example, if you have the following GitHub service interface:

public interface GitHub {     @GET("/repos/{owner}/{repo}/contributors")     Call<List<Contributor>> contributors(         @Path("owner") String owner,         @Path("repo") String repo); } 

You can then create the following MockGitHub for your tests:

public class MockGitHub implements GitHub {     private final BehaviorDelegate<GitHub> delegate;     private final Map<String, Map<String, List<Contributor>>> ownerRepoContributors;      public MockGitHub(BehaviorDelegate<GitHub> delegate) {         this.delegate = delegate;         ownerRepoContributors = new LinkedHashMap<>();          // Seed some mock data.         addContributor("square", "retrofit", "John Doe", 12);         addContributor("square", "retrofit", "Bob Smith", 2);         addContributor("square", "retrofit", "Big Bird", 40);         addContributor("square", "picasso", "Proposition Joe", 39);         addContributor("square", "picasso", "Keiser Soze", 152);     }      @Override public Call<List<Contributor>> contributors(String owner, String repo) {         List<Contributor> response = Collections.emptyList();         Map<String, List<Contributor>> repoContributors = ownerRepoContributors.get(owner);         if (repoContributors != null) {             List<Contributor> contributors = repoContributors.get(repo);             if (contributors != null) {                 response = contributors;             }         }         return delegate.returningResponse(response).contributors(owner, repo);     } } 

You can then use the MockGitHub on your tests to simulate the kinds of responses you are looking for. For the full example, see the implementations of the SimpleService and SimpleMockService for this Retrofit example.

EDIT: If you absolutely must connect to the actual server, you can set Retrofit to work synchronously with a custom ImmediateExecutor:

public class ImmediateExecutor implements Executor {     @Override public void execute(Runnable command) {         command.run();     } } 

Then apply it to the OkHttpClient you use when building the Retrofit:

OkHttpClient client = OkHttpClient.Builder()         .dispatcher(new Dispatcher(new ImmediateExecutor()))         .build();  Retrofit retrofit = new Retrofit.Builder()         .client(client)         //Your params         .build(); 

Answers 2

The answer is too easy than i expected:

Using CountDownLatch makes your test wait until you call countDown()

public class SimpleRetrofitTest {  private static final String login = "your@login; private static final String pass = "pass"; private final CountDownLatch latch = new CountDownLatch(1); private ApiManager apiManager; private OAuthToken oAuthToken;  @Before public void beforeTest() {     apiManager = ApiManager.getInstance(); }  @Test public void test_login() throws InterruptedException {     Assert.assertNotNull(apiManager);     apiManager.loginUser(login, pass, new ApiCallback<OAuthToken>() {         @Override         public void onSuccess(OAuthToken token) {             oAuthToken = token;             latch.countDown();         }          @Override         public void onFailure(@ResultCode.Code int errorCode, String errorMessage) {             latch.countDown();         }     });     latch.await();     Assert.assertNotNull(oAuthToken); }  @After public void afterTest() {     oAuthToken = null; }} 
Read More