material-components/material-components-android · error · IllegalStateException

There must be a keyline marked as focal.

Error message

There must be a keyline marked as focal.

What it means

KeylineState.Builder.build() throws this IllegalStateException when the builder never received a keyline marked as focal (isFocal=true). Focal keylines define the fully-unmasked center range of a Carousel and are structurally required: build() uses tmpFirstFocalKeyline to compute every keyline's resolved location. Without a focal keyline the KeylineState cannot be constructed.

Source

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

        int count,
        boolean isFocal) {
      if (count <= 0 || maskedItemSize <= 0F) {
        return this;
      }

      for (int i = 0; i < count; i++) {
        float loc = offsetLoc + (maskedItemSize * i);
        addKeyline(loc, mask, maskedItemSize, isFocal);
      }

      return this;
    }

    /** Builds and returns a {@link KeylineState}. */
    @NonNull
    public KeylineState build() {
      if (tmpFirstFocalKeyline == null) {
        throw new IllegalStateException("There must be a keyline marked as focal.");
      }

      List<Keyline> keylines = new ArrayList<>();
      for (int i = 0; i < tmpKeylines.size(); i++) {
        Keyline tmpKeyline = tmpKeylines.get(i);
        Keyline keyline =
            new Keyline(
                calculateKeylineLocationForItemPosition(
                    tmpFirstFocalKeyline.locOffset, itemSize, firstFocalKeylineIndex, i),
                tmpKeyline.locOffset,
                tmpKeyline.mask,
                tmpKeyline.maskedItemSize,
                tmpKeyline.isAnchor,
                tmpKeyline.cutoff,
                tmpKeyline.leftOrTopPaddingShift,
                tmpKeyline.rightOrBottomPaddingShift);
        keylines.add(keyline);
      }

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Mark the center (unmasked) keylines as focal: addKeylineRange(offsetLoc, 0F, itemSize, count, true) or addKeyline(offsetLoc, 0F, itemSize, true, false)
  2. If you do not need custom keylines, stop building KeylineState manually and use KeylineStateList.create(...) / the default Carousel configuration
  3. If a conditional strategy sometimes has no focal keylines, guard before build() and fall back to the default KeylineState
  4. Verify the focal count argument is >= 1 when computing ranges dynamically

Example fix

// before
new KeylineState.Builder(carouselSize, itemSize)
    .addKeyline(startLoc, 1F, maskedSize, false, true)
    .addKeylineRange(centerStart, 0F, itemSize, count, false) // never focal
    .addKeyline(endLoc, 1F, maskedSize, false, true)
    .build(); // throws: no focal keyline
// after
new KeylineState.Builder(carouselSize, itemSize)
    .addKeyline(startLoc, 1F, maskedSize, false, true)
    .addKeylineRange(centerStart, 0F, itemSize, count, true) // focal range
    .addKeyline(endLoc, 1F, maskedSize, false, true)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

KeylineState.Builder builder = new KeylineState.Builder(carouselSize, itemSize);
boolean[] addedFocal = {false};
// wrap every focal addition:
//   addKeylineRange(offsetLoc, 0F, itemSize, count, true) -> addedFocal[0] = true;
if (!addedFocal[0]) {
  throw new IllegalStateException("KeylineState config bug: no focal keyline added");
}
KeylineState state = builder.build();

Try / catch

try {
  KeylineState state = builder.build();
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().contains("focal")) {
    Log.e(TAG, "Falling back to default keylines: " + e.getMessage());
    state = KeylineStateList.create(carousel, itemSize);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling KeylineState.Builder.build() after only adding non-focal keylines: every addKeyline(...) invoked with isFocal=false, only addAnchorKeyline(...) calls (anchors cannot be focal), or addKeylineRange(offsetLoc, mask, maskedItemSize, count, isFocal=false). Typical when implementing a custom Carousel strategy (e.g. customizing Carousel.Config or building a KeylineState manually instead of using KeylineStateList).

Common situations: Customizing Carousel keylines beyond the default strategy; porting/calculating keyline math by hand and forgetting to mark the mask==0 center range as focal; count passed to addKeylineRange computing to 0 for the focal range so no focal keyline is ever added; version upgrades where strategy APIs changed to require explicit focal ranges.

Related errors


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