{"record":{"id":"e286fd7ac2090945","repo":"material-components/material-components-android","slug":"keylines-marked-as-focal-must-be-placed-next-to-ea","errorCode":null,"errorMessage":"Keylines marked as focal must be placed next to each other. There cannot be non-focal keylines between focal keylines.","messagePattern":"Keylines marked as focal must be placed next to each other\\. There cannot be non-focal keylines between focal keylines\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/carousel/KeylineState.java","lineNumber":404,"sourceCode":"        }\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.\");\n        }\n        if (maskedItemSize != tmpFirstFocalKeyline.maskedItemSize) {\n          throw new IllegalArgumentException(\n              \"Keylines that are marked as focal must all have the same masked item size.\");\n        }\n        tmpLastFocalKeyline = tmpKeyline;\n        lastFocalKeylineIndex = tmpKeylines.size();\n      } else {\n        if (tmpFirstFocalKeyline == null && tmpKeyline.maskedItemSize < lastKeylineMaskedSize) {\n          throw new IllegalArgumentException(\n              \"Keylines before the first focal keyline must be ordered by incrementing masked item\"\n                  + \" size.\");\n        } else if (tmpLastFocalKeyline != null\n            && tmpKeyline.maskedItemSize > lastKeylineMaskedSize) {\n          throw new IllegalArgumentException(\n              \"Keylines after the last focal keyline must be ordered by decreasing masked item\"","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/carousel/KeylineState.java#L386-L422","documentation":"Focal keylines are the contiguous run of full-size items at the center of a carousel. The builder enforces contiguity (KeylineState.java:404): when adding a focal keyline, if the previous focal keyline (lastFocalKeylineIndex) is more than one position back — i.e. a non-focal keyline was added between two focal ones — it throws IllegalArgumentException. The masking math and focal-range scroll calculations assume focal items form one unbroken block.","triggerScenarios":"Custom CarouselStrategy builder sequences like focal, small, focal — e.g. addKeyline(..., isFocal=true), then a non-focal keyline, then isFocal=true again. The check is tmpKeylines.size() - lastFocalKeylineIndex > 1 at the moment the later focal keyline is added.","commonSituations":"Generating keylines in a loop where the isFocal condition is computed per index and accidentally toggles (e.g. focal = index % 2 == 0), or inserting an extra decorative small keyline in the middle of what should be one focal run. The crash is at build time inside the strategy, so it fails on first carousel layout.","solutions":["Emit all focal keylines consecutively — use addKeylineRange for the focal run — and keep small/anchor keylines strictly before or after it.","Fix the focal predicate in the strategy loop so it yields a single contiguous true-run.","Model the sequence on MultiBrowseCarouselStrategy: small leading keylines, one contiguous focal range, small trailing keylines."],"exampleFix":"// before\nbuilder.addKeyline(o, 0f, itemSize, /* focal */ true, false);\nbuilder.addKeyline(o2, 0.5f, halfSize, /* focal */ false, false);\nbuilder.addKeyline(o3, 0f, itemSize, /* focal */ true, false); // throws: gap between focals\n\n// after\nbuilder.addKeyline(o2, 0.5f, halfSize, false, false);\nbuilder.addKeylineRange(o, o3, itemSize, itemSize, /* focal */ true); // contiguous focal run","handlingStrategy":"validation","validationCode":"boolean focal = size == itemSize && index >= firstFocalIndex && index <= lastFocalIndex; // contiguous run only\nbuilder.addKeyline(offset, mask, size, focal, anchor);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use addKeylineRange for the focal run so it is contiguous by construction.","Keep all non-focal keylines strictly before or after the focal run.","Ensure focal predicates yield a single contiguous true-run."],"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"}