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

Keylines before the first focal keyline must be ordered by i

Error message

Keylines before the first focal keyline must be ordered by incrementing masked item size.

What it means

Before the first focal keyline, keylines must grow monotonically in masked size as they approach the focal run (KeylineState.java:416). The builder tracks lastKeylineMaskedSize; when no focal keyline has been added yet (tmpFirstFocalKeyline == null) and the new non-focal keyline's maskedItemSize is smaller than the previous one, it throws. This ordering is what produces the carousel's characteristic 'items grow toward the center' masking curve.

Source

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

        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.");
        }
        if (maskedItemSize != tmpFirstFocalKeyline.maskedItemSize) {
          throw new IllegalArgumentException(
              "Keylines that are marked as focal must all have the same masked item size.");
        }
        tmpLastFocalKeyline = tmpKeyline;
        lastFocalKeylineIndex = tmpKeylines.size();
      } else {
        if (tmpFirstFocalKeyline == null && tmpKeyline.maskedItemSize < lastKeylineMaskedSize) {
          throw new IllegalArgumentException(
              "Keylines before the first focal keyline must be ordered by incrementing masked item"
                  + " size.");
        } else if (tmpLastFocalKeyline != null
            && tmpKeyline.maskedItemSize > lastKeylineMaskedSize) {
          throw new IllegalArgumentException(
              "Keylines after the last focal keyline must be ordered by decreasing masked item"
                  + " size.");
        }
      }
      lastKeylineMaskedSize = tmpKeyline.maskedItemSize;
      tmpKeylines.add(tmpKeyline);
      return this;
    }

    /**
     * Adds a keyline along the scrolling axis where an object should be masked by the given {@code
     * mask} and positioned at {@code offsetLoc}.
     *

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Order leading keylines ascending in maskedItemSize (smallest first, each >= the previous) up to the focal size.
  2. If sizes come from an array/list, sort or reverse it so it ascends before the focal run.
  3. Add a debug assertion in the strategy that logs each pre-focal size and fails fast on non-increasing sequences.

Example fix

// before
builder.addKeyline(o0, 0.9f, size*0.3f, false, false);
builder.addKeyline(o1, 0.7f, size*0.2f, false, false); // throws: decreased
builder.addKeyline(o2, 0.5f, size*0.6f, false, false);

// after
builder.addKeyline(o0, 0.9f, size*0.2f, false, false);
builder.addKeyline(o1, 0.7f, size*0.3f, false, false);
builder.addKeyline(o2, 0.5f, size*0.6f, false, false);
Defensive patterns

Strategy: validation

Validate before calling

double prevSize = 0;
for (KeylineSpec k : leadingKeylines) {
  if (k.maskedItemSize <= prevSize) throw new IllegalStateException("leading sizes must ascend");
  prevSize = k.maskedItemSize;
  builder.addKeyline(k.offset, k.mask, k.maskedItemSize, false, false);
}

Prevention

When it happens

Trigger: Custom CarouselStrategy emitting leading keylines in decreasing or jumbled size order — e.g. sizes [0.9, 0.5, 0.7] before the first focal keyline. The check applies only to keylines added before any isFocal=true keyline, and only for positive maskedItemSize (size <= 0F entries are skipped earlier).

Common situations: Loop-generated keylines whose size formula decreases with index when it should increase, or shuffled/copy-pasted keyline arrays. Also migrating a strategy between horizontal and vertical and flipping the iteration direction without flipping the size order.

Related errors


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