f2prateek / dart

Extras binding and intent builders for Android apps.
Apache License 2.0
1.19k stars 88 forks source link

Dart Injection Succeeds & Fails in Casting java.util.Stack #181

Closed ahasbini closed 6 years ago

ahasbini commented 7 years ago

Hi all.

I'm facing an issue with inject java.util.Stack objects using Dart.inject. I'm using Dart in restoring the savedInstanceState of an activity. During my development, I often use Instant Run which automatically kills the app and send the savedInstantState upon calling onCreate. I simply take the chance of speeding up my development by restoring the variables in the activity using Dart and being able to continue the flow of the application. One of the variables that is restored is of type Stack. With Instant Run the object is being restored properly. However, when the application is long gone and terminated, and then it is restored from recent apps, the application crashes while Dart is injecting back the object.

The following is the log detailing the scenario:

... //Changed a log in code, Instant Run Initiated
11-14 00:19:28.545 17671-17671/com.ahasbini.app I/MainActivity: onPause: called
11-14 00:19:28.545 17671-17671/com.ahasbini.app I/MainActivity: onPause: finished
11-14 00:19:28.554 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: called
11-14 00:19:28.554 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: stateStack: [Item1]
11-14 00:19:28.554 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: finished
11-14 00:19:28.559 17671-17671/com.ahasbini.app I/MainActivity: onDestroy: called
11-14 00:19:28.559 17671-17671/com.ahasbini.app I/MainActivity: onDestroy: finished
11-14 00:19:28.585 17671-17671/com.ahasbini.app I/MainActivity: onCreate: called
11-14 00:19:28.585 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: testing and restoring from savedInstanceState
11-14 00:19:28.585 17671-17671/com.ahasbini.app D/Dart: Looking up extra injector for com.ahasbini.app.activities.MainActivity
11-14 00:19:28.586 17671-17671/com.ahasbini.app D/Dart: HIT: Class loaded injection class.
11-14 00:19:28.586 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: stateStack: [Item1]
11-14 00:19:28.586 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: aa fragment: ItemsFragment{596d9f8 #0 id=0x7f0d0084}
11-14 00:19:28.587 17671-17671/com.ahasbini.app I/MainActivity: onCreate: finished
... //Changed a log in code, Instant Run Triggered
11-14 00:20:13.844 17671-17671/com.ahasbini.app I/MainActivity: onPause: called
11-14 00:20:13.844 17671-17671/com.ahasbini.app I/MainActivity: onPause: finished
11-14 00:20:13.846 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: called
11-14 00:20:13.846 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: stateStack: [Item1]
11-14 00:20:13.846 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: finished
11-14 00:20:13.851 17671-17671/com.ahasbini.app I/MainActivity: onDestroy: called
11-14 00:20:13.852 17671-17671/com.ahasbini.app I/MainActivity: onDestroy: finished
11-14 00:20:13.879 17671-17671/com.ahasbini.app I/MainActivity: onCreate: called
11-14 00:20:13.879 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: me again: testing and restoring from savedInstanceState
11-14 00:20:13.880 17671-17671/com.ahasbini.app D/Dart: Looking up extra injector for com.ahasbini.app.activities.MainActivity
11-14 00:20:13.880 17671-17671/com.ahasbini.app D/Dart: HIT: Cached in injector map.
11-14 00:20:13.880 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: stateStack: [Item1]
11-14 00:20:13.880 17671-17671/com.ahasbini.app I/MainActivity: restoreFromSavedState: aa fragment: ItemsFragment{f4a3359 #0 id=0x7f0d0084}
11-14 00:20:13.881 17671-17671/com.ahasbini.app I/MainActivity: onCreate: finished
... //Pressed Home Button
11-14 00:20:25.326 17671-17671/com.ahasbini.app I/MainActivity: onPause: called
11-14 00:20:25.326 17671-17671/com.ahasbini.app I/MainActivity: onPause: finished
11-14 00:20:25.332 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: stateStack: [Item1]
11-14 00:20:25.332 17671-17671/com.ahasbini.app I/MainActivity: onSaveInstanceState: finished
... //Killed app from Android Studio
11-14 00:20:33.806 17671-17678/com.ahasbini.app I/art: System.exit called, status: 1
11-14 00:20:33.806 17671-17678/com.ahasbini.app I/AndroidRuntime: VM exiting with result code 1, cleanup skipped.
11-14 00:20:37.315 19785-19785/com.ahasbini.app I/InstantRun: starting instant run server: is main process
... //Opened app from recent apps
11-14 00:20:37.841 19785-19785/com.ahasbini.app W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
11-14 00:20:37.993 19785-19785/com.ahasbini.app I/MainActivity: onCreate: called
11-14 00:20:37.993 19785-19785/com.ahasbini.app I/MainActivity: restoreFromSavedState: restoring from savedInstanceState
11-14 00:20:37.994 19785-19785/com.ahasbini.app D/Dart: Looking up extra injector for com.ahasbini.app.activities.MainActivity
11-14 00:20:37.994 19785-19785/com.ahasbini.app D/Dart: HIT: Class loaded injection class.
11-14 00:20:38.001 19785-19785/com.ahasbini.app D/AndroidRuntime: Shutting down VM
11-14 00:20:38.011 19785-19785/com.ahasbini.app E/AndroidRuntime: FATAL EXCEPTION: main
                                                                           Process: com.ahasbini.app, PID: 19785
                                                                           java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ahasbini.app/com.ahasbini.app.activities.MainActivity}: com.f2prateek.dart.Dart$UnableToInjectException: Unable to inject extras for com.ahasbini.app.activities.MainActivity@de95a3c
                                                                               at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2666)
                                                                               at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)
                                                                               at android.app.ActivityThread.-wrap12(ActivityThread.java)
                                                                               at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)
                                                                               at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                               at android.os.Looper.loop(Looper.java:154)
                                                                               at android.app.ActivityThread.main(ActivityThread.java:6121)
                                                                               at java.lang.reflect.Method.invoke(Native Method)
                                                                               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
                                                                               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
                                                                            Caused by: com.f2prateek.dart.Dart$UnableToInjectException: Unable to inject extras for com.ahasbini.app.activities.MainActivity@de95a3c
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:143)
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:129)
                                                                               at com.ahasbini.app.activities.MainActivity.restoreFromSavedState(MainActivity.java:162)
                                                                               at com.ahasbini.app.activities.MainActivity.onCreate(MainActivity.java:110)
                                                                               at android.app.Activity.performCreate(Activity.java:6682)
                                                                               at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
                                                                               at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619)
                                                                               at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) 
                                                                               at android.app.ActivityThread.-wrap12(ActivityThread.java) 
                                                                               at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478) 
                                                                               at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                               at android.os.Looper.loop(Looper.java:154) 
                                                                               at android.app.ActivityThread.main(ActivityThread.java:6121) 
                                                                               at java.lang.reflect.Method.invoke(Native Method) 
                                                                               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) 
                                                                               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) 
                                                                            Caused by: java.lang.reflect.InvocationTargetException
                                                                               at java.lang.reflect.Method.invoke(Native Method)
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:138)
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:129) 
                                                                               at com.ahasbini.app.activities.MainActivity.restoreFromSavedState(MainActivity.java:162) 
                                                                               at com.ahasbini.app.activities.MainActivity.onCreate(MainActivity.java:110) 
                                                                               at android.app.Activity.performCreate(Activity.java:6682) 
                                                                               at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118) 
                                                                               at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) 
                                                                               at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) 
                                                                               at android.app.ActivityThread.-wrap12(ActivityThread.java) 
                                                                               at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478) 
                                                                               at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                               at android.os.Looper.loop(Looper.java:154) 
                                                                               at android.app.ActivityThread.main(ActivityThread.java:6121) 
                                                                               at java.lang.reflect.Method.invoke(Native Method) 
                                                                               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) 
                                                                               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) 
                                                                            Caused by: java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Stack
                                                                               at com.ahasbini.app.activities.MainActivity$$ExtraInjector.inject(MainActivity$$ExtraInjector.java:14)
                                                                               at java.lang.reflect.Method.invoke(Native Method) 
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:138) 
                                                                               at com.f2prateek.dart.Dart.inject(Dart.java:129) 
                                                                               at com.ahasbini.app.activities.MainActivity.restoreFromSavedState(MainActivity.java:162) 
                                                                               at com.ahasbini.app.activities.MainActivity.onCreate(MainActivity.java:110) 
                                                                               at android.app.Activity.performCreate(Activity.java:6682) 
                                                                               at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118) 
                                                                               at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) 
                                                                               at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) 
                                                                               at android.app.ActivityThread.-wrap12(ActivityThread.java) 
                                                                               at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478) 
                                                                               at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                               at android.os.Looper.loop(Looper.java:154) 
                                                                               at android.app.ActivityThread.main(ActivityThread.java:6121) 
                                                                               at java.lang.reflect.Method.invoke(Native Method) 
                                                                               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) 
                                                                               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) 

Appreciate if someone could help me with this, thanks.

ahasbini commented 7 years ago

@f2prateek sorry if it's a bad time, just wondering if if there's a solution for such an issue or if I'm doing anything wrong?

ahasbini commented 7 years ago

Have updated from 2.0.1 to 2.0.3. Still getting the same error.

I've logged the Bundle during onSavedInstanceState call and when it is coming in again in onCreate once the Activity is re-created. Realized that they contain the same data in both (Array of Items) however it seems that somewhere and somehow they are not being of the same type (Stack) when Activity is re-created. Instead it is being of ArrayList type. And when the generated Injector is running it is directly casting the object to Stack as in below:

public class MainActivity$$ExtraInjector {
  public static void inject(Dart.Finder finder, MainActivity target, Object source) {
    Object object;
    object = finder.getExtra(source, "stateStack");
    if (object != null) {
      target.stateStack = (Stack<MainActivity.State>) object;
    }
  }
}

I've added the following to manually extract the Stack object before calling Dart.inject() instead of letting Dart to re-inject it:

// A work around to handle restoration with object being mutated for some reason
stateStack = new Stack<>();
if (savedInstanceState.containsKey("stateStack")) {
    Logger.LogI(TAG, "restoreFromSavedState: found stateStack");
    List<State> stateList = (List<State>) savedInstanceState.get("stateStack");
    stateStack.addAll(stateList);
    savedInstanceState.remove("stateStack");
}

Would be nice if when generating the Injector it could handle classes of Collection or List differently by creating a new instance of the object and then adding them all from the received Collection/List in the Bundle. Although I do think that there might be something wrong from Android side that is causing this mutation.

Please feel free to close the issue as my problem has been solved with this workaround. Thanks!

stephanenicolas commented 7 years ago

Your issue is a bit complicated. What would really help us would be a sample or even better: a failing junit test added to the tests suite of dart. We are missing too many details to solve the issue just by reading the description.