material-components/material-components-android · error · IllegalArgumentException
secondaryIconGravity cannot have the same alignment as iconG
Error message
secondaryIconGravity cannot have the same alignment as iconGravity
What it means
MaterialButton supports a primary icon (iconGravity) and an expanded-layout secondary icon (secondaryIconGravity), but both drawables are placed into the same set of compound drawable slots. If both gravities resolve to the same alignment (start/start, end/end, or top/top), the two icons would fight over one compound-drawables position and one would silently overwrite the other. validateSecondaryIconGravity() therefore throws IllegalArgumentException whenever icon != null && secondaryIcon != null && areIconsGravitySameAlignment() (MaterialButton.java:1263 checks isIconStart&&isSecondaryIconStart, isIconEnd&&isSecondaryIconEnd, isIconTop&&isSecondaryIconTop). The library prefers failing loudly over rendering a wrong icon.
Source
Thrown at lib/java/com/google/android/material/button/MaterialButton.java:1359
|| (isSecondaryIconTop() && drawableTop != secondaryIcon);
// Force icon update if needsIconReset otherwise updated it only if icon has changed.
if (needsIconReset || hasIconChanged) {
if (isSecondaryIconStart()) {
setCompoundDrawablesRelative(
secondaryIcon, getUpdatedSecondaryIconFor(1), getUpdatedSecondaryIconFor(2), null);
} else if (isSecondaryIconEnd()) {
setCompoundDrawablesRelative(
getUpdatedSecondaryIconFor(0), getUpdatedSecondaryIconFor(1), secondaryIcon, null);
} else if (isSecondaryIconTop()) {
setCompoundDrawablesRelative(
getUpdatedSecondaryIconFor(0), secondaryIcon, getUpdatedSecondaryIconFor(2), null);
}
}
}
private void validateSecondaryIconGravity() {
if (secondaryIcon != null && icon != null && areIconsGravitySameAlignment()) {
throw new IllegalArgumentException(
"secondaryIconGravity cannot have the same alignment as iconGravity");
}
}
@Nullable
private Drawable getUpdatedSecondaryIconFor(int position) {
switch (position) {
case 0: // start
return (icon != null && isIconStart()) ? icon : null;
case 1: // top
return (icon != null && isIconEnd()) ? icon : null;
case 2: // end
return (icon != null && isIconEnd()) ? icon : null;
default:
return null;
}
}
View on GitHub (pinned to ac7e18efee)
Solutions
- Pick different alignments for the two icons, e.g. keep iconGravity="start" (or "textStart") and set app:secondaryIconGravity="end" / call setSecondaryIcon(GRAVITY_END).
- If both icons genuinely belong on the same side, merge them into a single layered drawable (LayerDrawable) and set it as the primary icon with only one gravity.
- Drop one of the icons: if the secondary icon is not needed for the collapsed/expanded affordance, call setSecondaryIcon(null) before changing gravities.
- When toggling gravities at runtime, always set the new secondaryIconGravity in the same transaction as the icon change and test all state combinations (icon-only, secondary-only, both).
Example fix
// before button.setIcon(AppCompatResources.getDrawable(this, R.drawable.leading)); button.setIconGravity(MaterialButton.ICON_GRAVITY_START); button.setSecondaryIcon(AppCompatResources.getDrawable(this, R.drawable.expand)); button.setSecondaryIconGravity(MaterialButton.ICON_GRAVITY_START); // throws // after button.setIcon(AppCompatResources.getDrawable(this, R.drawable.leading)); button.setIconGravity(MaterialButton.ICON_GRAVITY_START); button.setSecondaryIcon(AppCompatResources.getDrawable(this, R.drawable.expand)); button.setSecondaryIconGravity(MaterialButton.ICON_GRAVITY_END);
Defensive patterns
Strategy: validation
Validate before calling
// Before setting both icons, confirm alignments differ
boolean sameAlignment =
(button.getIconGravity() == MaterialButton.ICON_GRAVITY_START
&& secondaryGravity == MaterialButton.ICON_GRAVITY_START)
|| (button.getIconGravity() == MaterialButton.ICON_GRAVITY_END
&& secondaryGravity == MaterialButton.ICON_GRAVITY_END)
|| (button.getIconGravity() == MaterialButton.ICON_GRAVITY_TOP
&& secondaryGravity == MaterialButton.ICON_GRAVITY_TOP);
if (!sameAlignment) {
button.setSecondaryIconGravity(secondaryGravity);
} Try / catch
try { button.setSecondaryIcon(drawable); } catch (IllegalArgumentException e) { Log.w(TAG, "icon gravities collide; using primary icon only"); } Prevention
- Standardize on iconGravity=start + secondaryIconGravity=end in theme styles so individual screens cannot collide.
- Never set one icon's gravity without re-checking the other's current value.
- Add a lint-style unit test asserting your default styles never share an alignment.
When it happens
Trigger: Calling setIcon() and setSecondaryIcon() (or inflating app:icon plus app:secondaryIcon from XML) while iconGravity and secondaryIconGravity share an alignment — e.g. iconGravity="start" with secondaryIconGravity="start", or both set to TEXT_GRAVITY_START-like values that map to the same edge. Also triggered later by setSecondaryIconGravity(...) or setIconGravity(...) that moves one icon onto the other's edge while both drawables are non-null.
Common situations: Copy-pasting a MaterialButton XML block for the expandable/collapsible button style and only changing app:secondaryIcon, leaving both gravity attributes defaulted or identical. Setting the secondary icon gravity programmatically after restoring state (e.g. in onBindViewHolder for a recycling view) without re-checking the primary gravity. Version upgrades where the secondary-icon API (used with MotionLayout expand transitions) started enforcing this constraint.
Related errors
- Attempted to set ShapeAppearanceModel on a MaterialButton wh
- Attempted to get ShapeAppearanceModel from a MaterialButton
- Attempted to set ShapeAppearance on a MaterialButton which h
- Attempted to get ShapeAppearance from a MaterialButton which
- The wrap overflow mode is not compatible to the vertical ori
AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14).
Data as JSON: /api/errors/7959e007c2fd35e9.
Report an issue: GitHub.