frankiesardo/icepick

java.util.HashMap cannot be cast to java.util.LinkedHashMap

Opened this issue · 2 comments

I get an error "java.lang.ClassCastException" when restoring activity.

@State LinkedHashMap<Double, String[]> dropsMap;

Process: com.com.com, PID: 13985
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.com.com/com.com.com.CalculatorActivity}: java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.LinkedHashMap
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2947)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3008)
at android.app.ActivityThread.-wrap14(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6688)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)
Caused by: java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.LinkedHashMap
at com.com.com.CalculatorActivity$$Icepick.restore(CalculatorActivity$$Icepick.java:30)
at com.com.com.CalculatorActivity$$Icepick.restore(CalculatorActivity$$Icepick.java:11)
at icepick.Icepick.restoreInstanceState(Icepick.java:73)
at com.com.com.CalculatorActivity.onCreate(CalculatorActivity.java:156)
at android.app.Activity.performCreate(Activity.java:6912)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2900)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3008) 
at android.app.ActivityThread.-wrap14(ActivityThread.java) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:154) 
at android.app.ActivityThread.main(ActivityThread.java:6688) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358) 

How can this be avoided?

The problem is that when Android serializes the Bundle when saving or restoring the state, it only supports a subset of types: Integer, Long, String, Map, Bundle, List.

If you serialize an ArrayList of LinkedHashMap it will be restored as List and Map.
This is problematic if using LinkedHashMap because you loose the element ordering.

See: https://stackoverflow.com/questions/12300886/linkedlist-put-into-intent-extra-gets-recast-to-arraylist-when-retrieving-in-nex/12305459#12305459 for more details

You can use a custom bundler to avoid the problem:

Create a custom bundler:

public class LinkedHashmapBundler implements Bundler<LinkedHashMap> {

 @Override
 public void put(String s, LinkedHashMap val, Bundle bundle) {
    bundle.putSerializable(s, new Wrapper<>(val));
 }

 @SuppressWarnings("unchecked")
 @Override
 public LinkedHashMap get(String s, Bundle bundle) {
    return ((Wrapper<LinkedHashMap>) bundle.getSerializable(s)).get();
 }
} 

public class Wrapper<T extends Serializable> implements Serializable {
 private T wrapped;

 Wrapper(T wrapped) {
    this.wrapped = wrapped;
 }

 public T get() {
    return wrapped;
 }
} 

Use it like this:
@State(LinkedHashmapBundler.class) LinkedHasMap map

Thank you, it works. I've noticed something strange. If you have project that uses android library and that library defines icepick processor and annotationProcessor, then icepick won't work inside the app's domain. Once annotationProcessor is duplicated to app module, everything works.