androidcrashandroid-motionlayoutshrinkresources

Why does my app crash with Resources$NotFoundException in MotionLayout when using strict shrinker, and how can I fix it?


I recently enabled strict shrinker in my Android project to aggressively remove unused resources as described in the official documentation here https://developer.android.com/build/shrink-code#strict-reference-checks

However, after enabling it, my app started crashing whenever I tried to open the screen containing MotionLayout, with the following error:

Caused by: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f0a03f0
    at android.content.res.ResourcesImpl.getResourceTypeName(ResourcesImpl.java:316)
    at androidx.constraintlayout.motion.widget.MotionScene$Transition.fill(MotionScene.java:978)
    at androidx.constraintlayout.motion.widget.MotionScene.load(MotionScene.java:1090)
    at androidx.constraintlayout.motion.widget.MotionLayout.init(MotionLayout.java:3840)
    at androidx.constraintlayout.motion.widget.MotionLayout.<init>(MotionLayout.java:1124)
    at com.mypackage.MyViewBinding.inflate(MyViewBinding.java:102)
    at com.mypackage.MyView.<init>(MyView.kt:37)

Solution

  • The crash occurs because MotionLayout relies on reflection to handle its transitions and keyframes, which leads to issues when using the strict shrinker. Specifically, MotionLayout uses reflection to initialize keyframes in its internal KeyFrames.java file:

    sKeyMakers.put(KeyAttributes.NAME, KeyAttributes.class.getConstructor())
    sKeyMakers.put(KeyPosition.NAME, KeyPosition.class.getConstructor())
    sKeyMakers.put(KeyCycle.NAME, KeyCycle.class.getConstructor())
    sKeyMakers.put(KeyTimeCycle.NAME, KeyTimeCycle.class.getConstructor())
    sKeyMakers.put(KeyTrigger.NAME, KeyTrigger.class.getConstructor())
    

    Because these resource IDs are not explicitly referenced in the code, the strict shrinker considers them unused and removes them. This results in a Resources$NotFoundException when MotionLayout tries to access these resources at runtime.

    Solution

    To prevent the shrinker from removing the necessary resource IDs, create a keep.xml file and specify the IDs of ConstraintSets used in your MotionLayout scenes with the tools:keep attribute. For example:

    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@+id/set1,@+id/set2,@+id/set3"
        tools:shrinkMode="strict" />
    

    From the scene like:

    <MotionScene
      ...
    
      <ConstraintSet
          android:id="@+id/set1">
    
      <ConstraintSet
          android:id="@+id/set2"
          motion:deriveConstraintsFrom="@+id/set1">
    
      <ConstraintSet
          android:id="@+id/set3"
          motion:deriveConstraintsFrom="@+id/set2">
    
    </MotionScene>
    

    Place this file in your res directory. It ensures that all specified resources remain in the final build and are available to MotionLayout.


    Additionally, there is a Github issue about incompatibility between MotionLayout and R8 mode If you encounter some problems with it, give a try adding a line to your proguard configuration

    -keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
      public <init>();
    }