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

MaterialSplitButton can only hold two MaterialButtons.

Error message

MaterialSplitButton can only hold two MaterialButtons.

What it means

MaterialSplitButton models exactly one action plus one trailing chevron button: REQUIRED_BUTTON_COUNT is 2 (MaterialSplitButton.java:75), and addView throws IllegalArgumentException when getChildCount() is already > 2, i.e. when a third child is being added. The container's checked-state handling, shape stitching and accessibility logic all assume the two-button structure, so extra children are rejected outright.

Source

Thrown at lib/java/com/google/android/material/button/MaterialSplitButton.java:102

  public MaterialSplitButton(
      @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
  }

  /**
   * This override prohibits Views other than {@link MaterialButton} to be added where the leading
   * {@link MaterialButton} has either an icon and/or a label and the trailing {@link
   * MaterialButton} has an animated vector drawable as an icon. It also makes updates to the add
   * button shape and margins.
   */
  @Override
  public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
    if (!(child instanceof MaterialButton)) {
      throw new IllegalArgumentException("MaterialSplitButton can only hold MaterialButtons.");
    }
    if (getChildCount() > REQUIRED_BUTTON_COUNT) {
      throw new IllegalArgumentException("MaterialSplitButton can only hold two MaterialButtons.");
    }

    MaterialButton buttonChild = (MaterialButton) child;
    super.addView(child, index, params);
    if (indexOfChild(child) == 1) {
      buttonChild.setCheckable(true);
      buttonChild.setA11yClassName(Button.class.getName());
      // Set initial content description based on checked state when focused.
      ViewCompat.setStateDescription(
          buttonChild,
          getResources()
              .getString(
                  buttonChild.isChecked()
                      ? R.string.mtrl_button_expanded_content_description
                      : R.string.mtrl_button_collapsed_content_description));

      buttonChild.addOnCheckedChangeListener(
          (button, isChecked) -> {

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Keep exactly two MaterialButton children: the main action and the trailing chevron button; put extra actions in the popup menu shown when the chevron is pressed.
  2. Remove extra children (badges, spacers, labels) from the XML and attach them outside the split button or via the button's icon/text APIs.
  3. If you truly need N visible segments, use MaterialButtonToggleGroup or MaterialButtonGroup instead.

Example fix

// before
splitButton.addView(primary);
splitButton.addView(chevron);
splitButton.addView(extraAction); // throws

// after
splitButton.addView(primary);
splitButton.addView(chevron);
splitButton.getTrailingButton().setOnClickListener(v -> showOverflowMenu(extraActions));
Defensive patterns

Strategy: validation

Validate before calling

if (splitButton.getChildCount() < 2) {
  splitButton.addView(new MaterialButton(context));
}

Type guard

static boolean canAddToSplitButton(MaterialSplitButton g) {
  return g.getChildCount() <= 2;
}

Prevention

When it happens

Trigger: Declaring three or more children in the MaterialSplitButton's XML (inflation calls addView per child, so the third child trips the check), or calling addView more than twice programmatically. Note the guard uses getChildCount() > REQUIRED_BUTTON_COUNT, so the throw fires on the attempt to add a fourth slot's worth of existing children — any child beyond the intended two-part structure is invalid.

Common situations: Trying to build a multi-action split (e.g. 'Save | Save As | Export') by adding a third MaterialButton — developers expect a menu-like container but the component only supports the leading action plus the built-in trailing chevron that opens a menu you provide separately. Dynamic content that inserts status TextViews or badges into the split button also triggers it.

Related errors


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