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
- Mark the center (unmasked) keylines as focal: addKeylineRange(offsetLoc, 0F, itemSize, count, true) or addKeyline(offsetLoc, 0F, itemSize, true, false)
- If you do not need custom keylines, stop building KeylineState manually and use KeylineStateList.create(...) / the default Carousel configuration
- If a conditional strategy sometimes has no focal keylines, guard before build() and fall back to the default KeylineState
- 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
- Always mark the center unmasked range with addKeylineRange(..., true)
- Favor KeylineStateList.create over hand-built keyline lists unless customization is required
- Add a unit test asserting your strategy's build() succeeds for every carousel width you support
- Remember anchor keylines can never be focal (separate IllegalArgumentException)
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
- Anchor keylines cannot be focal.
- Anchor keylines must be either the first or last keyline.
- Keylines marked as focal must be placed next to each other.
- Keylines that are marked as focal must all have the same mas
- Keylines before the first focal keyline must be ordered by i
AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14).
Data as JSON: /api/errors/147d2795af34ce6f.
Report an issue: GitHub.