{"record":{"id":"658163167b0db3cb","repo":"Blankj/AndroidUtilCode","slug":"classname-and-apiinfo-implapiclass","errorCode":null,"errorMessage":"<\" + className + \"> and <\" + apiInfo.implApiClass + \"> impl same api of <\" + superClassName + \">","messagePattern":"<\" \\+ className \\+ \"> and <\" \\+ apiInfo\\.implApiClass \\+ \"> impl same api of <\" \\+ superClassName \\+ \">","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"plugin/api-gradle-plugin/src/main/java/com/blankj/api/ApiClassVisitor.java","lineNumber":70,"sourceCode":"                public void visit(String name, Object value) {// 可获取注解的值\n                    isMock = (boolean) value;\n                    super.visit(name, value);\n                }\n            };\n        }\n        return super.visitAnnotation(desc, visible);\n    }\n\n    @Override\n    public void visitEnd() {\n        super.visitEnd();\n        if (hasAnnotation) {\n            if (!isMock) {// 如果不是 mock 的话\n                ApiInfo apiInfo = mApiImplMap.get(superClassName);\n                if (apiInfo == null || apiInfo.isMock) {// 不存在或者之前存在的是 mock\n                    mApiImplMap.put(superClassName, new ApiInfo(className, false));\n                } else {// 存在一个 api 多个非 mock 实现就报错\n                    throw new IllegalArgumentException(\"<\" + className + \"> and <\" + apiInfo.implApiClass + \"> impl same api of <\" + superClassName + \">\");\n                }\n            } else {// mock 的话，如果 map 中已存在就不覆盖了\n                if (!mApiImplMap.containsKey(superClassName)) {\n                    mApiImplMap.put(superClassName, new ApiInfo(className, true));\n                }\n            }\n        }\n    }\n}\n","sourceCodeStart":52,"sourceCodeEnd":80,"githubUrl":"https://github.com/Blankj/AndroidUtilCode/blob/7b4caf9e5440046b3fefb63b6b6e2ead7ebaf809/plugin/api-gradle-plugin/src/main/java/com/blankj/api/ApiClassVisitor.java#L52-L80","documentation":"The api-gradle-plugin scans bytecode for classes carrying @ApiUtils.Api and maps each BaseApi subclass to a single real implementation. ApiUtils is designed for exactly one non-mock impl per API. In ApiClassVisitor.visitEnd(), if a second non-mock (isMock == false) implementation of the same BaseApi subclass is found, it throws IllegalArgumentException naming both conflicting classes and their common API.","triggerScenarios":"Two classes both annotated @ApiUtils.Api (isMock defaults to false) extend the same XxxApi (which extends ApiUtils.BaseApi), and both are compiled into the app transform scope scanned by the plugin.","commonSituations":"Forgetting to mark a debug/stub impl with isMock = true; copy-pasting an impl class; two modules each ship a real impl of a shared API; a mock impl left compiled into the full/release build variant.","solutions":["Designate one of the two conflicting classes as a mock via @ApiUtils.Api(isMock = true); the plugin then keeps only the single non-mock impl.","Delete or exclude the duplicate non-mock impl from the build variant that triggered the scan.","Keep mock impls in a dedicated mock source set so they are not compiled into full/release builds."],"exampleFix":"// before - two real impls of MainApi (isMock defaults to false) -> conflict\n@ApiUtils.Api\npublic class MainApiImpl extends MainApi { /* ... */ }\n\n@ApiUtils.Api\npublic class MainApiDebugImpl extends MainApi { /* ... */ }\n\n// after - mark the debug one as mock so only one real impl remains\n@ApiUtils.Api(isMock = true)\npublic class MainApiDebugImpl extends MainApi { /* ... */ }","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Enforce one real (non-mock) impl per BaseApi subclass across the whole app.","Always annotate stub/debug implementations with @ApiUtils.Api(isMock = true).","Isolate mock impls in a mock source set so they never ship alongside the real impl.","Name impl classes distinctly and grep for duplicate `extends XxxApi` before merging."],"tags":["gradle-plugin","api","bytecode","build","asm"],"backgroundTag":null,"analyzedSha":"7b4caf9e5440046b3fefb63b6b6e2ead7ebaf809","analyzedAt":"2026-08-14T02:26:54.956Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}