material-components/material-components-android · error · IllegalArgumentException

Motion easing theme attribute must have 4 control points if

Error message

Motion easing theme attribute must have 4 control points if using bezier curve format; instead got: %d

What it means

For legacy string easings in the cubic-bezier format, MotionUtils splits the content on commas and requires exactly 4 control points. 'cubic-bezier(0.2,0,0,1)' yields 4 tokens; anything else (too few, too many, or empty segments) throws IllegalArgumentException with the count received.

Source

Thrown at lib/java/com/google/android/material/motion/MotionUtils.java:139

          "Motion easing theme attribute must be an @interpolator resource for"
              + " ?attr/motionEasing*Interpolator attributes or a string for"
              + " ?attr/motionEasing* attributes.");
    }

    String easingString = String.valueOf(easingValue.string);
    if (isLegacyEasingAttribute(easingString)) {
      return getLegacyThemeInterpolator(easingString);
    }

    return AnimationUtils.loadInterpolator(context, easingValue.resourceId);
  }

  private static TimeInterpolator getLegacyThemeInterpolator(String easingString) {
    if (isLegacyEasingType(easingString, EASING_TYPE_CUBIC_BEZIER)) {
      String controlPointsString = getLegacyEasingContent(easingString, EASING_TYPE_CUBIC_BEZIER);
      String[] controlPoints = controlPointsString.split(",");
      if (controlPoints.length != 4) {
        throw new IllegalArgumentException(
            "Motion easing theme attribute must have 4 control points if using bezier curve"
                + " format; instead got: "
                + controlPoints.length);
      }

      float controlX1 = getLegacyControlPoint(controlPoints, 0);
      float controlY1 = getLegacyControlPoint(controlPoints, 1);
      float controlX2 = getLegacyControlPoint(controlPoints, 2);
      float controlY2 = getLegacyControlPoint(controlPoints, 3);
      return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
    } else if (isLegacyEasingType(easingString, EASING_TYPE_PATH)) {
      String path = getLegacyEasingContent(easingString, EASING_TYPE_PATH);
      return new PathInterpolator(PathParser.createPathFromPathData(path));
    } else {
      throw new IllegalArgumentException("Invalid motion easing type: " + easingString);
    }
  }

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Write exactly four comma-separated numbers inside cubic-bezier(...)
  2. Prefer the @interpolator resource form (PathInterpolator XML) which is parsed by the framework and less error-prone
  3. Unit-test custom easing strings with a split(",").length == 4 assertion

Example fix

<!-- before -->
<item name="motionEasingStandard">cubic-bezier(0.4, 0)</item>
<!-- after -->
<item name="motionEasingStandard">cubic-bezier(0.4, 0, 0.2, 1)</item>
Defensive patterns

Strategy: validation

Validate before calling

// Validate a cubic-bezier easing string before applying it
fun isValidBezierEasing(s: String): Boolean {
    if (!s.startsWith("cubic-bezier(") || !s.endsWith(")")) return false
    val parts = s.removePrefix("cubic-bezier(").removeSuffix(")").split(",")
    if (parts.size != 4) return false
    return parts.all { it.trim().toFloatOrNull() != null }
}

Try / catch

catch (e: IllegalArgumentException) { Log.w(TAG, "Malformed easing string; using default", e); useDefaultEasing() }

Prevention

When it happens

Trigger: A theme attribute value like 'cubic-bezier(0.4)' or 'cubic-bezier(0.2, 0, 0, 1, 0.5)' passed to resolveThemeInterpolator; also 'cubic-bezier(,)' style typos where empty strings still count as tokens.

Common situations: Hand-authoring easing strings in themes; translating CSS cubic-bezier values and dropping a component; trailing commas; whitespace variants that alter split counts only when segments are missing.

Related errors


AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14). Data as JSON: /api/errors/3c1470d4a849288f. Report an issue: GitHub.