I am using RetroFit 1.9 with a GSON converter that has been working well for me so far. Now I am trying to marshall the List
of custom Parcelable
objects received in the Callback
, and I am met with a ClassCastException
:
02-02 09:53:49.921 13030-13030/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.app, PID: 13030
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to android.os.Parcelable
at android.os.Parcel.writeTypedList(Parcel.java:1166)
at com.example.common.util.ParcelableUtil.marshall(ParcelableUtil.java:37)
at com.example.app.service.WearableMessageService$1.success(WearableMessageService.java:130)
at com.example.app.service.WearableMessageService$1.success(WearableMessageService.java:120)
at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
This is my callback method:
new Callback<List<MyObject>>() {
@Override
public void success(List<MyObject> objects, Response response) {
mByteArray = ParcelableUtil.marshall(objects);
}
@Override
public void success(RetrofitError error) {
Timber.w("Failed to retrieve events, with message " + error.getMessage());
}
}
So I added a for loop in the success
method, just before I call ParcelableUtil.marshall()
, to test why I am getting a list of LinkedTreeMap
objects instead of my objects:
for(MyObject object : objects) {
Timber.d(object.getTitle());
}
Not only does this print the correct title of each object, miraculously the rest of the code works! ParcelableUtil
no longer throws an error, and I receive a byte array that I am later able to unmarshall perfectly.
How does a list of LinkedTreeMap
objects change to a list of my objects after it is observed in a loop? Why am I getting a list of LinkedTreeMap
objects in the first place? What is going on here?
MyObject
class:
public class MyObject implements Parcelable {
@SerializedName("title") private String mTitle;
@SerializedName("location") private String mLocation;
@SerializedName("start_date") private Date mStartDate;
@SerializedName("end_date") private Date mEndDate;
public MyObject() {
/* Required empty constructor */
}
public Event(Parcel in) {
mTitle = in.readString();
mLocation = in.readString();
mStartDate = new Date(in.readLong());
mEndDate = new Date(in.readLong());
}
public String getTitle() {
return mTitle;
}
public String getLocation() {
return mLocation;
}
public void getStartDate() {
return mStartDate();
}
public void getEndDate() {
return mEndDate();
}
@Override
public in describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mTitle);
dest.writeString(mLocation);
dest.writeLong(mStartDate.getTime());
dest.writeLong(mEndDate.getTime());
}
public static final Creator<MyObject> CREATOR = new Creator<MyObject>() {
@Override
public MyObject createFromParcel(Parcel in) {
return new MyObject(in);
}
@Override
public MyObject[] newArray(int size) {
return new MyObject[size];
}
};
}
A sample JSON feed for MyObject
:
{
"items": [
{
"title":"Object 1"
"location":"Location 1"
"start_date":"2016-02-02 15:30:00"
"end_date":"2016-02-02 19:00:00"
}
{
"title":"Object 2"
"location":"Location 2"
"start_date":"2016-02-02 18:00:00"
"end_date":"2016-02-03 18:00:00"
}
]
}
The GSON TypeAdapterFactory
in use:
public class DataAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override
public void read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if(jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if(jsonObject.has("items") && jsonObject.get("items".isJsonArray()) {
jsonElement = jsonObject.getAsJsonArray("items");
}
return delegate.fromJsonTree(jsonElement);
}
}
}.nullSafe();
}
}
Ah, ProGuard, the bane of my existence...
I dawned on me this morning. ProGuard could have been removing the class, and using the class in the for loop must have told ProGuard that the class was actually needed. I added the following to my ProGuard rules and it now works without cycling through a loop:
-keep public class com.example.common.model.MyObject
-keep public class * implements com.example.common.model.MyObject
-keepclassmembers class com.example.common.model.MyObject {
<methods>;
}
I believe in my case I would only need the first line, but I think the other ones will just cover my bases.