{"record":{"id":"e833a81309a543f6","repo":"material-components/material-components-android","slug":"keylines-after-the-last-focal-keyline-must-be-orde","errorCode":null,"errorMessage":"Keylines after the last focal keyline must be ordered by decreasing masked item size.","messagePattern":"Keylines after the last focal keyline must be ordered by decreasing masked item size\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/carousel/KeylineState.java","lineNumber":421,"sourceCode":"        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\"\n                  + \" size.\");\n        }\n      }\n      lastKeylineMaskedSize = tmpKeyline.maskedItemSize;\n      tmpKeylines.add(tmpKeyline);\n      return this;\n    }\n\n    /**\n     * Adds a keyline along the scrolling axis where an object should be masked by the given {@code\n     * mask} and positioned at {@code offsetLoc}.\n     *\n     * <p>Note that calls to {@link #addKeyline(float, float, float, boolean, boolean)} and {@link\n     * #addKeylineRange(float, float, float, int)} are added in order. Typically, this means\n     * keylines should be added in order of ascending {@code offsetLoc}. The first and last keylines\n     * added are 'anchor' keylines that mark the start and ends of the keylines. These keylines do\n     * not shift when scrolled.","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/carousel/KeylineState.java#L403-L439","documentation":"Mirror of error 38: after the last focal keyline, masked sizes must decrease monotonically toward the far edge (KeylineState.java:421). When a focal run has already been added (tmpLastFocalKeyline != null) and a subsequent non-focal keyline's maskedItemSize is larger than the previous one (lastKeylineMaskedSize), the builder throws. The trailing keylines shrink away from the full-size focal center, and any increase breaks the expected layout math.","triggerScenarios":"Custom CarouselStrategy emitting trailing keylines in increasing or jumbled size order after the focal run — e.g. sizes [1.0(focal), 0.5, 0.9]. Applies only to keylines following the last isFocal=true keyline, with positive maskedItemSize.","commonSituations":"Same class of bug as the leading-order error: formula direction flipped, arrays reversed when porting orientation, or copy-pasted keyline blocks with the wrong sort direction on the trailing side. The builder fails on first carousel measure, pointing at the exact addKeyline call.","solutions":["Order trailing keylines descending in maskedItemSize (largest just after the focal run, smallest at the end).","If generating keylines symmetrically, build the leading half ascending and mirror it (reverse) for the trailing half.","Unit-test the strategy's KeylineState build with the library's own assertions so ordering regressions surface in CI, not on device."],"exampleFix":"// before\nbuilder.addKeylineRange(of0, of1, itemSize, itemSize, true, false); // focal run\nbuilder.addKeyline(o3, 0.4f, size*0.5f, false, false);\nbuilder.addKeyline(o4, 0.8f, size*0.9f, false, false); // throws: increased after focal\n\n// after\nbuilder.addKeylineRange(of0, of1, itemSize, itemSize, true, false); // focal run\nbuilder.addKeyline(o3, 0.4f, size*0.9f, false, false);\nbuilder.addKeyline(o4, 0.8f, size*0.5f, false, false);","handlingStrategy":"validation","validationCode":"double prevSize = itemSize;\nfor (KeylineSpec k : trailingKeylines) {\n  if (k.maskedItemSize >= prevSize) throw new IllegalStateException(\"trailing sizes must descend\");\n  prevSize = k.maskedItemSize;\n  builder.addKeyline(k.offset, k.mask, k.maskedItemSize, false, false);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Emit trailing keylines in descending masked size.","Build trailing halves by mirroring (reversing) the validated leading half.","Cover both halves in strategy unit tests so ordering regressions fail in CI."],"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"}