I recently updated to AGP 8 and I released an update on to the play store. I am getting reports of some crashes in an area of code that was not changed recently. I am fairly confident this is a crash caused by the AGP 8 upgrade, specifically around R8 fullmode.
The code surrounding the crash is restoring the state of a layout manager.
Restoration code
if (savedInstanceState != null) {
oldRecyclerLayoutState = savedInstanceState.getParcelableCompat(
SIS_RECYCLER_LAYOUT_STATE,
)
}
Save instance code
private lateinit var layoutManager: LinearLayoutManager
...
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(SIS_RECYCLER_LAYOUT_STATE, layoutManager.onSaveInstanceState())
super.onSaveInstanceState(outState)
}
The crash report indicates that reading a parcelable is crashing due to the ifTable of a class being null when Class.isAssignableFrom() is called on it. From the AOSP, I can see that an ifTable is a table of interfaces. I assume isAssignableFrom is using the ifTable to determine assignability however it was unable to read it due to the class being null. Unfortunately the error doesn't tell me which class is null.
Full stack trace:
Fatal Exception: java.lang.NullPointerException: Attempt to read from field 'java.lang.Object[] java.lang.Class.ifTable' on a null object reference in method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)'
at java.lang.Class.isAssignableFrom(Class.java:579)
at android.os.Parcel.readParcelableCreatorInternal(Parcel.java:4865)
at android.os.Parcel.readParcelableInternal(Parcel.java:4778)
at android.os.Parcel.readValue(Parcel.java:4544)
at android.os.Parcel.readValue(Parcel.java:4324)
at android.os.Parcel.-$$Nest$mreadValue()
at android.os.Parcel$LazyValue.apply(Parcel.java:4422)
at android.os.Parcel$LazyValue.apply(Parcel.java:4381)
at android.os.BaseBundle.getValueAt(BaseBundle.java:394)
at android.os.BaseBundle.getValue(BaseBundle.java:374)
at android.os.BaseBundle.getValue(BaseBundle.java:357)
at android.os.BaseBundle.get(BaseBundle.java:696)
at android.os.Bundle.getParcelable(Bundle.java:947)
at com.ggstudios.lolcatalyst.util.ext.BundleExtKt.getParcelableCompat(BundleExt.kt:21)
at com.ggstudios.lolcatalyst.summonerlookup.SummonerProfileFragment.onViewCreated(SummonerProfileFragment.kt:422)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3137)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1435)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2979)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2890)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3138)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1435)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2979)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2897)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
at com.ggstudios.lolcatalyst.activity.abs.BaseActivity.onStart(BaseActivity.kt:90)
at com.ggstudios.lolcatalyst.activity.MainActivity.onStart(MainActivity.kt)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1510)
at android.app.Activity.performStart(Activity.java:8603)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:4191)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2571)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8741)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
If anyone knows the cause or what the fix is, that would be greatly appreciated!
tl;dr the fix is to use
BundleCompat.getParcelable(Bundle, String, Class)fromandroidx.core:core-ktx:1.10.0instead ofgetParcelable(String, Class).I think this crash is caused by an unfortunate combination of two things.
getParcelable(String, Class)methods introduced in API 33 has some bugs if the Parcelable is not defined in a particular way. This is documented here.It looks like the fragile implementation of
getParcelable(String, Class)and R8 optimizing certain code is causing this crash. The fix for now appear to be to use the oldgetParcelable(String)method in API 33. Google has stated they have fixed the issue withgetParcelable(String, Class)in API 34.Update:
androidx.core:core-ktx:1.10.0contains a fix for this issue. It containsBundleCompat.getParcelable(Bundle, String, Class)which will only call the newgetParcelable(Bundle, Class)function on Android U and above.