frankiesardo / icepick

Android Instance State made easy
Eclipse Public License 1.0
3.75k stars 208 forks source link

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

Open ghost opened 6 years ago

ghost commented 6 years ago

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?

sergioserra commented 6 years ago

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

oradkovsky commented 5 years ago

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.