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

Anchor keylines must be either the first or last keyline.

Error message

Anchor keylines must be either the first or last keyline.

What it means

KeylineState.Builder tracks latestAnchorKeylineIndex; anchors are legal only at the boundaries of the keyline list, so when a new anchor is added while a previous anchor exists and is not at index 0, the builder throws IllegalArgumentException (KeylineState.java:388). In practice this rejects a third anchor or an anchor placed after keylines that already follow a first anchor — the valid pattern is exactly one anchor at the very start and optionally one at the very end.

Source

Thrown at lib/java/com/google/android/material/carousel/KeylineState.java:388

    @CanIgnoreReturnValue
    public Builder addKeyline(
        float offsetLoc,
        @FloatRange(from = 0.0F, to = 1.0F) float mask,
        float maskedItemSize,
        boolean isFocal,
        boolean isAnchor,
        float cutoff,
        float leftOrTopPaddingShift,
        float rightOrBottomPaddingShift) {
      if (maskedItemSize <= 0F) {
        return this;
      }
      if (isAnchor) {
        if (isFocal) {
          throw new IllegalArgumentException("Anchor keylines cannot be focal.");
        }
        if (latestAnchorKeylineIndex != NO_INDEX && latestAnchorKeylineIndex != 0) {
          throw new IllegalArgumentException(
              "Anchor keylines must be either the first or last keyline.");
        }
        latestAnchorKeylineIndex = tmpKeylines.size();
      }

      Keyline tmpKeyline =
          new Keyline(UNKNOWN_LOC, offsetLoc, mask, maskedItemSize, isAnchor, cutoff,
              leftOrTopPaddingShift, rightOrBottomPaddingShift);
      if (isFocal) {
        if (tmpFirstFocalKeyline == null) {
          tmpFirstFocalKeyline = tmpKeyline;
          firstFocalKeylineIndex = tmpKeylines.size();
        }

        if (lastFocalKeylineIndex != NO_INDEX && tmpKeylines.size() - lastFocalKeylineIndex > 1) {
          throw new IllegalArgumentException(
              "Keylines marked as focal must be placed next to each other. There cannot be"
                  + " non-focal keylines between focal keylines.");

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Use at most two anchors: one as the first keyline added and optionally one as the last; remove intermediate isAnchor=true flags.
  2. Order the builder calls so anchors are added before (first) and after (last) all other keylines.
  3. Print the add sequence of (isAnchor, isFocal, maskedItemSize) per keyline in a debug log to spot the stray anchor.

Example fix

// before
builder.addKeyline(o0, m0, s0, false, true);  // anchor
builder.addKeyline(o1, m1, s1, false, false);
builder.addKeyline(o2, m2, s2, false, true);  // anchor after others -> throws

// after
builder.addKeyline(o0, m0, s0, false, true);  // first anchor
builder.addKeyline(o1, m1, s1, true, false);  // focal
builder.addKeyline(o2, m2, s2, false, true);  // last anchor — index of previous anchor is 0, OK
Defensive patterns

Strategy: validation

Validate before calling

// Enforce: anchors only at index 0 and the final add
boolean isFirstAdd = keylinesAddedSoFar == 0;
boolean isLastAdd = (i == totalKeylines - 1);
boolean anchor = isFirstAdd || isLastAdd;
builder.addKeyline(offset, mask, size, focal, anchor);

Prevention

When it happens

Trigger: Calling addKeyline(..., isAnchor=true) three or more times in one builder, or adding an anchor after non-anchor keylines when an earlier anchor is not at index 0 (e.g. first anchor added after some leading keylines). The anchor-after-anchor pattern that isn't first/last is what the latestAnchorKeylineIndex != 0 check catches.

Common situations: Custom CarouselStrategy code that marks every 'edge-ish' keyline as an anchor in a loop, or copies UncontainedCarouselStrategy-style anchors into a strategy whose keyline ordering differs, putting an anchor in the middle. Debugging is easier knowing the builder checks at add time, so the stack trace points at the exact offending addKeyline call.

Related errors


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