Retrofit 2 and the (Three) @Body problem

abangkis p
4 min readOct 23, 2016

--

My favorite way of using Retrofit 2 is to use the @Body annotation. Basically what you need to do is create an API end point in your service class, and then mark the paramater object with @Body annotation like the sample code below

@Headers(WoBVersion.VERSION_HEADER)
@POST("api/categories")
Call<ServerResponse> getCategories(@Body CategoryReq req);

And then in the server side you can just request the input stream from the servlet and then parse it as the json object. With my own helper class, getting the parameter is as simple as writing this one liner

CategoryReq req = httpBodyReader.readAsObject(CategoryReq.class);

With this approach you basically will be working with regular java object. No need to mess around with path or query parameter. You can add or reduce a field in the request object anytime you want without event thinking about how it would affect the URL. It’s simple and very efficient.

The Problems

This approach has been working very well in my debug environment. The first problem arise when I generate a Release APK. The App is working fine, but there’s no data from the server. It seems that the app cannot reach the server. So I googled a bunch of Retrofit things, from config to using @Body properly. But no result, no clue in any of the links.

The first hint that i got is when I tried to debug the app again. The app work flawlessly. My immediate hunch is that this problem is related to ProGuard.

This isn’t the first time I’m using ProGuard. In this case I already setup my ProGuard Rules for Retrofit

## -------------Begin: Retrofit2 ---
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keepattributes *Annotation*

-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
-keepclassmembernames interface * {
@retrofit.http.* <methods>;
}

## -------------End: Retrofit2 ---

And GSON

##--- Begin:GSON ----
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# keep enum so gson can deserialize it
-keepclassmembers enum * { *; }

##--- End:GSON ----

This is the second problem. If you are used with java excellent error reporting and stack trace, using ProGuard sometime feels like staring at a black hole.

If you didn’t know, there’s a list of proguard rules for popular android library.

You can check it out at https://github.com/krschultz/android-proguard-snippets.

If you find a problem with your ProGuard rules. I recommend you check out the snippet. Cross check with the library that you’re using and see what’s missing. With a little bit of trial and error it’ll eventually work.

But this time it didn’t work. Digging deeper, debugging the server. Turn out that the request is actually received by the server. It just that the JSON format is changed so the server didn’t recognized it.

The format changed from

{ “region_id” : 2, “category” : “all”}

to

{ “a” : 2, “b” : “all”}

This is the third and last problem. Turn out proguard obfuscate our CategoryReq POJO object. Changing the field name and thus in effect, end up changing the final JSon string.

And this is the tricky part. Because at the beginning we think that this is a Retrofit 2 problem, which isn’t true. It’s a GSon and ProGuard problem. But it will only happened if you use Retrofit 2 in a certain way. In this case using @Body annotation with a Java POJO object. A simple Google query wouldn’t link directly to this problem. (One of the reason I’m making this post)

The Fix

There are 2 way to fix this.

First you can add a @SerializedName(“field_name”) for each field that you don’t want to be obfuscate.

Or you can do

To make short, to fix it you just have to add a -keep class configuration in your proguard rules of the package where your Pojo class is stored like the 3 last line from this example

##--- Begin:GSON ----
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# keep enum so gson can deserialize it
-keepclassmembers enum * { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class net.mreunionlabs.wob.model.request.** { *; }
-keep class net.mreunionlabs.wob.model.response.** { *; }
-keep class net.mreunionlabs.wob.model.gson.** { *; }
##--- End:GSON ----

Hope that helps :)

--

--

abangkis p

Founder of MReunion Labs. Senior Developer Id-Android. Take a chance! Click that follow button.