{"record":{"id":"ec57579479cd28aa","repo":"material-components/material-components-android","slug":"anchor-keylines-must-be-either-the-first-or-last-k","errorCode":null,"errorMessage":"Anchor keylines must be either the first or last keyline.","messagePattern":"Anchor keylines must be either the first or last keyline\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/carousel/KeylineState.java","lineNumber":388,"sourceCode":"    @CanIgnoreReturnValue\n    public Builder addKeyline(\n        float offsetLoc,\n        @FloatRange(from = 0.0F, to = 1.0F) float mask,\n        float maskedItemSize,\n        boolean isFocal,\n        boolean isAnchor,\n        float cutoff,\n        float leftOrTopPaddingShift,\n        float rightOrBottomPaddingShift) {\n      if (maskedItemSize <= 0F) {\n        return this;\n      }\n      if (isAnchor) {\n        if (isFocal) {\n          throw new IllegalArgumentException(\"Anchor keylines cannot be focal.\");\n        }\n        if (latestAnchorKeylineIndex != NO_INDEX && latestAnchorKeylineIndex != 0) {\n          throw new IllegalArgumentException(\n              \"Anchor keylines must be either the first or last keyline.\");\n        }\n        latestAnchorKeylineIndex = tmpKeylines.size();\n      }\n\n      Keyline tmpKeyline =\n          new Keyline(UNKNOWN_LOC, offsetLoc, mask, maskedItemSize, isAnchor, cutoff,\n              leftOrTopPaddingShift, rightOrBottomPaddingShift);\n      if (isFocal) {\n        if (tmpFirstFocalKeyline == null) {\n          tmpFirstFocalKeyline = tmpKeyline;\n          firstFocalKeylineIndex = tmpKeylines.size();\n        }\n\n        if (lastFocalKeylineIndex != NO_INDEX && tmpKeylines.size() - lastFocalKeylineIndex > 1) {\n          throw new IllegalArgumentException(\n              \"Keylines marked as focal must be placed next to each other. There cannot be\"\n                  + \" non-focal keylines between focal keylines.\");","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/carousel/KeylineState.java#L370-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use at most two anchors: one as the first keyline added and optionally one as the last; remove intermediate isAnchor=true flags.","Order the builder calls so anchors are added before (first) and after (last) all other keylines.","Print the add sequence of (isAnchor, isFocal, maskedItemSize) per keyline in a debug log to spot the stray anchor."],"exampleFix":"// before\nbuilder.addKeyline(o0, m0, s0, false, true);  // anchor\nbuilder.addKeyline(o1, m1, s1, false, false);\nbuilder.addKeyline(o2, m2, s2, false, true);  // anchor after others -> throws\n\n// after\nbuilder.addKeyline(o0, m0, s0, false, true);  // first anchor\nbuilder.addKeyline(o1, m1, s1, true, false);  // focal\nbuilder.addKeyline(o2, m2, s2, false, true);  // last anchor — index of previous anchor is 0, OK","handlingStrategy":"validation","validationCode":"// Enforce: anchors only at index 0 and the final add\nboolean isFirstAdd = keylinesAddedSoFar == 0;\nboolean isLastAdd = (i == totalKeylines - 1);\nboolean anchor = isFirstAdd || isLastAdd;\nbuilder.addKeyline(offset, mask, size, focal, anchor);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Limit anchors to the first and last keylines added.","Never mark interior keylines as anchors.","Debug-log each (index, isAnchor) pair while building states."],"tags":["android","material-components","carousel","keylines","custom-strategy","builder"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}