Skip to content

[EXTERNAL] fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension - #1934

Merged
AlvaroBrey merged 1 commit into
RevenueCat:external/gabrieldonadel/agp9-built-in-kotlinfrom
gabrieldonadel:fix/agp9-built-in-kotlin
Sep 3, 2026
Merged

[EXTERNAL] fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension#1934
AlvaroBrey merged 1 commit into
RevenueCat:external/gabrieldonadel/agp9-built-in-kotlinfrom
gabrieldonadel:fix/agp9-built-in-kotlin

Conversation

@gabrieldonadel

@gabrieldonadel gabrieldonadel commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Android Gradle Plugin 9 ships built-in Kotlin support and enables it by default, so AGP
registers the kotlin extension itself. When a library also applies kotlin-android
explicitly, the two collide and configuration fails before anything compiles:

> Failed to apply plugin 'kotlin-android'.
   > Cannot add extension with name 'kotlin', as there is an extension already registered with that name.

The apply is unconditional in both files, so on an AGP 9 project this library cannot be
built at all. There is no consumer-side workaround short of patching the file — setting
android.builtInKotlin=false project-wide just to build one dependency is not a
reasonable ask, and that escape hatch is removed in AGP 10.

Change

Apply the plugin only when nothing has registered the kotlin extension yet:

if (project.extensions.findByName('kotlin') == null) {
    apply plugin: 'kotlin-android'
}

Files changed:

  • android/build.gradle
  • react-native-purchases-ui/android/build.gradle

This checks the exact condition that fails, so there is no AGP version table to keep in
sync. It follows the approach already used in
purchases-capacitor#860 and
purchases-flutter#1765, per
maintainer review on this PR.

Behaviour across configurations:

AGP android.builtInKotlin kotlin extension explicit apply
8.x unset or false absent yes (unchanged)
9.x unset or true registered by AGP no
9.x false absent yes
10+ n/a (opt-out removed) registered by AGP no

The check sits after apply plugin: 'com.android.library' in both files, so AGP has
already registered its extensions by the time it runs.

What I verified, and what I did not

  • Verified end to end on a real Expo SDK 58 / React Native 0.87 project with AGP
    9.2.1 and Gradle 9.4.1: :app:assembleDebug succeeds both with
    -Pandroid.newDsl=true -Pandroid.builtInKotlin=true and with both flags off.
  • Confirmed both branches actually execute, rather than one path silently always being
    taken: with the flags off, :<module>:compileDebugKotlin runs from the explicitly
    applied plugin; with them on, the build succeeds without it, which it could not do if
    the plugin were still being applied.
  • Syntax-checked both files with Groovy's Phases.CONVERSION.
  • Not run: this repo's own CI or example app. You mentioned you'd handle AGP 9 CI
    coverage separately.

Found while sweeping 157 popular React Native libraries for AGP 9 new-DSL compatibility.
34 failed with the new DSL enabled, and 29 of those failed on exactly this — it is the
most common blocker by a wide margin.

@gabrieldonadel
gabrieldonadel requested a review from a team as a code owner September 2, 2026 20:38
@gabrieldonadel
gabrieldonadel force-pushed the fix/agp9-built-in-kotlin branch from 8d53220 to d73f157 Compare September 2, 2026 21:03
@AlvaroBrey
AlvaroBrey self-requested a review September 3, 2026 08:51
@AlvaroBrey

Copy link
Copy Markdown
Member

Thanks, this is the same issue we hit in Capacitor (RevenueCat/purchases-capacitor#860) and Flutter (RevenueCat/purchases-flutter#1765). We went with checking whether AGP already registered the kotlin extension instead of deriving it from the AGP version and property:

 if (project.extensions.findByName('kotlin') == null) {
     apply plugin: 'kotlin-android'
 }

It tests the exact condition that fails, so there's no version table to keep in sync with AGP and is simpler. Mind switching both files to that? I'll handle CI coverage for AGP 9 separately.

@gabrieldonadel

Copy link
Copy Markdown
Contributor Author

Thanks, this is the same issue we hit in Capacitor (RevenueCat/purchases-capacitor#860) and Flutter (RevenueCat/purchases-flutter#1765). We went with checking whether AGP already registered the kotlin extension instead of deriving it from the AGP version and property:

 if (project.extensions.findByName('kotlin') == null) {
     apply plugin: 'kotlin-android'
 }

It tests the exact condition that fails, so there's no version table to keep in sync with AGP and is simpler. Mind switching both files to that? I'll handle CI coverage for AGP 9 separately.

Oh sure, that seems simpler; let me update it

…in extension

AGP 9 ships built-in Kotlin support and registers the `kotlin` extension
itself. Applying kotlin-android on top of it fails configuration with
"Cannot add extension with name 'kotlin'". Check for the extension directly
rather than deriving it from the AGP version and the android.builtInKotlin
property, so there is no version table to keep in sync.
@gabrieldonadel
gabrieldonadel force-pushed the fix/agp9-built-in-kotlin branch from d73f157 to 8bff8b9 Compare September 3, 2026 12:57
@gabrieldonadel gabrieldonadel changed the title fix(android): skip explicit Kotlin plugin when AGP provides built-in Kotlin fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension Sep 3, 2026
@gabrieldonadel

Copy link
Copy Markdown
Contributor Author

Done — both files now use the extension check, and I've updated the PR title and description to match.

if (project.extensions.findByName('kotlin') == null) {
    apply plugin: 'kotlin-android'
}

Agreed it's the better shape: it tests the condition that actually fails, and it covers AGP 10 for free without a version table — which the version-based guard needed a special case for.

One thing I wanted to confirm rather than assume, since the whole approach rests on it: that AGP has registered the kotlin extension by the time this line is evaluated, and not in some later callback. If it registered late, findByName would return null on AGP 9 and we'd apply the plugin anyway, reproducing the original error. It holds — the check sits after apply plugin: 'com.android.library' in both files, and I tested it on a real Expo SDK 58 / RN 0.87 project with AGP 9.2.1 and Gradle 9.4.1:

Config Result
newDsl=true + builtInKotlin=true :app:assembleDebug succeeds — extension present, apply skipped
both flags off :app:assembleDebug succeeds — extension absent, plugin applied

I also checked both branches genuinely execute rather than one path always being taken: with the flags off, :<module>:compileDebugKotlin runs from the explicitly applied plugin; with them on the build completes without it, which it couldn't if the plugin were still being applied.

Still haven't run this repo's own CI — thanks for picking up the AGP 9 coverage.

For context on where this came from: it surfaced in a sweep of 157 popular React Native libraries against the AGP 9 defaults. 34 failed with the new DSL on, and 29 of those failed on exactly this collision. I'd used a version-and-property guard across those PRs; I'll take your simpler version back to the others.

@AlvaroBrey

Copy link
Copy Markdown
Member

Cool! As per our security policy I'll be merging this PR into an intermediate branch to run CI on while keeping credit to you. Thanks again for your contribution!

@AlvaroBrey AlvaroBrey changed the title fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension [EXTERNAL] fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension Sep 3, 2026
@AlvaroBrey
AlvaroBrey changed the base branch from main to external/gabrieldonadel/agp9-built-in-kotlin September 3, 2026 14:20
@AlvaroBrey
AlvaroBrey merged commit 22131fc into RevenueCat:external/gabrieldonadel/agp9-built-in-kotlin Sep 3, 2026
1 check passed
AlvaroBrey added a commit that referenced this pull request Sep 3, 2026
…isters the kotlin extension (#1934) via @gabrieldonadel (#1939)

External contribution from @gabrieldonadel, merged into this branch to
run CI. Original PR: #1934

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Gradle plugin wiring only; no runtime or security impact, with
behavior unchanged on pre-AGP-9 toolchains.
> 
> **Overview**
> Fixes Android Gradle Plugin 9 builds that fail with **"Cannot add
extension with name 'kotlin'"** when both AGP’s built-in Kotlin support
and the `kotlin-android` plugin run together.
> 
> In **`android/build.gradle`** and
**`react-native-purchases-ui/android/build.gradle`**, `kotlin-android`
is no longer applied unconditionally. It is applied only when
`project.extensions.findByName('kotlin')` is null, so older AGP setups
still get the plugin while AGP 9 can rely on its own `kotlin` extension.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
22131fc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Gabriel Donadel Dall'Agnol <donadeldev@gmail.com>
@gabrieldonadel
gabrieldonadel deleted the fix/agp9-built-in-kotlin branch September 3, 2026 19:51
This was referenced Sep 4, 2026
This was referenced Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants