material-components/material-components-android · error · UnsupportedOperationException

Only one RevealableListItem with start gravity is supported.

Error message

Only one RevealableListItem with start gravity is supported.

What it means

ListItemLayout supports at most one RevealableListItem child aligned to the start (left gravity in LTR). addView stores it in swipeToRevealLayoutLeft; a second start-aligned RevealableListItem makes the two candidates ambiguous and the layout throws UnsupportedOperationException.

Source

Thrown at lib/java/com/google/android/material/listitem/ListItemLayout.java:250

        positionState = MIDDLE_STATE_SET;
        break;
    }
    refreshDrawableState();
  }

  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    super.addView(child, index, params);
    if (child instanceof RevealableListItem) {
      if (isRightAligned(child)) {
        if (swipeToRevealLayoutRight != null) {
          throw new UnsupportedOperationException(
              "Only one RevealableListItem with end gravity is supported.");
        }
        swipeToRevealLayoutRight = child;
      } else {
        if (swipeToRevealLayoutLeft != null) {
          throw new UnsupportedOperationException(
              "Only one RevealableListItem with start gravity is supported.");
        }
        swipeToRevealLayoutLeft = child;
      }
      // Start the reveal view at a desired width of 0
      ((RevealableListItem) child).setRevealedWidth(0);
      // Make sure reveal view has lower elevation
      child.setElevation(getElevation() - 1);
    } else if (contentView != null && child instanceof SwipeableListItem) {
      throw new UnsupportedOperationException(
          "Only one SwipeableListItem view is allowed in a ListItemLayout.");
    } else if (child instanceof SwipeableListItem) {
      contentView = child;
    }
  }

  @Override
  public void onViewRemoved(View child) {

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Have exactly one start-gravity RevealableListItem; embed multiple start actions within it.
  2. Dynamically replacing: remove the previous start reveal view before adding a new one.
  3. Run a layout lint/inspection for duplicate RevealableListItem siblings with identical gravity.

Example fix

// before (dynamic)
listItemLayout.addView(newStartReveal); // old start reveal still attached -> throws

// after
if (listItemLayout.findViewById(R.id.start_reveal) != null) {
  listItemLayout.removeView(listItemLayout.findViewById(R.id.start_reveal));
}
listItemLayout.addView(newStartReveal);
Defensive patterns

Strategy: validation

Validate before calling

View existing = listItemLayout.findViewById(R.id.start_reveal);
if (existing != null) listItemLayout.removeView(existing);
listItemLayout.addView(newStartRevealView);

Prevention

When it happens

Trigger: Adding two RevealableListItem children both with start/left gravity to one ListItemLayout, via XML or addView; swapping a reveal view at runtime without removing the old one.

Common situations: A/B testing two reveal designs where the old view is not removed; refactoring layouts and leaving a stray reveal container with layout_gravity="start".

Related errors


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