{"record":{"id":"3c1470d4a849288f","repo":"material-components/material-components-android","slug":"motion-easing-theme-attribute-must-have-4-control","errorCode":null,"errorMessage":"Motion easing theme attribute must have 4 control points if using bezier curve format; instead got: %d","messagePattern":"Motion easing theme attribute must have 4 control points if using bezier curve format; instead got: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/motion/MotionUtils.java","lineNumber":139,"sourceCode":"          \"Motion easing theme attribute must be an @interpolator resource for\"\n              + \" ?attr/motionEasing*Interpolator attributes or a string for\"\n              + \" ?attr/motionEasing* attributes.\");\n    }\n\n    String easingString = String.valueOf(easingValue.string);\n    if (isLegacyEasingAttribute(easingString)) {\n      return getLegacyThemeInterpolator(easingString);\n    }\n\n    return AnimationUtils.loadInterpolator(context, easingValue.resourceId);\n  }\n\n  private static TimeInterpolator getLegacyThemeInterpolator(String easingString) {\n    if (isLegacyEasingType(easingString, EASING_TYPE_CUBIC_BEZIER)) {\n      String controlPointsString = getLegacyEasingContent(easingString, EASING_TYPE_CUBIC_BEZIER);\n      String[] controlPoints = controlPointsString.split(\",\");\n      if (controlPoints.length != 4) {\n        throw new IllegalArgumentException(\n            \"Motion easing theme attribute must have 4 control points if using bezier curve\"\n                + \" format; instead got: \"\n                + controlPoints.length);\n      }\n\n      float controlX1 = getLegacyControlPoint(controlPoints, 0);\n      float controlY1 = getLegacyControlPoint(controlPoints, 1);\n      float controlX2 = getLegacyControlPoint(controlPoints, 2);\n      float controlY2 = getLegacyControlPoint(controlPoints, 3);\n      return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);\n    } else if (isLegacyEasingType(easingString, EASING_TYPE_PATH)) {\n      String path = getLegacyEasingContent(easingString, EASING_TYPE_PATH);\n      return new PathInterpolator(PathParser.createPathFromPathData(path));\n    } else {\n      throw new IllegalArgumentException(\"Invalid motion easing type: \" + easingString);\n    }\n  }\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/motion/MotionUtils.java#L121-L157","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Write exactly four comma-separated numbers inside cubic-bezier(...)","Prefer the @interpolator resource form (PathInterpolator XML) which is parsed by the framework and less error-prone","Unit-test custom easing strings with a split(\",\").length == 4 assertion"],"exampleFix":"<!-- before -->\n<item name=\"motionEasingStandard\">cubic-bezier(0.4, 0)</item>\n<!-- after -->\n<item name=\"motionEasingStandard\">cubic-bezier(0.4, 0, 0.2, 1)</item>","handlingStrategy":"validation","validationCode":"// Validate a cubic-bezier easing string before applying it\nfun isValidBezierEasing(s: String): Boolean {\n    if (!s.startsWith(\"cubic-bezier(\") || !s.endsWith(\")\")) return false\n    val parts = s.removePrefix(\"cubic-bezier(\").removeSuffix(\")\").split(\",\")\n    if (parts.size != 4) return false\n    return parts.all { it.trim().toFloatOrNull() != null }\n}","typeGuard":null,"tryCatchPattern":"catch (e: IllegalArgumentException) { Log.w(TAG, \"Malformed easing string; using default\", e); useDefaultEasing() }","preventionTips":["Copy easing strings from verified material theme sources","Prefer @interpolator XML resources over hand-typed strings","Unit-test custom easing strings for format and point count"],"tags":["android","material-components","animation","easing","cubic-bezier"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}