Skip to content

[EXTERNAL] Fix build on AGP 9 by skipping kotlin-android when AGP registers the kotlin extension (#1934) via @gabrieldonadel - #1939

Merged
AlvaroBrey merged 1 commit into
mainfrom
external/gabrieldonadel/agp9-built-in-kotlin
Sep 3, 2026
Merged

[EXTERNAL] Fix build on AGP 9 by skipping kotlin-android when AGP registers the kotlin extension (#1934) via @gabrieldonadel#1939
AlvaroBrey merged 1 commit into
mainfrom
external/gabrieldonadel/agp9-built-in-kotlin

Conversation

@AlvaroBrey

@AlvaroBrey AlvaroBrey commented Sep 3, 2026

Copy link
Copy Markdown
Member

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


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.

Reviewed by Cursor Bugbot for commit 22131fc. Bugbot is set up for automated code reviews on this repo. Configure here.

…in extension (#1934)

## 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:

```groovy
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](RevenueCat/purchases-capacitor#860) and
[purchases-flutter#1765](RevenueCat/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.
@AlvaroBrey
AlvaroBrey requested a review from a team as a code owner September 3, 2026 14:21
@AlvaroBrey AlvaroBrey self-assigned this Sep 3, 2026

@tonidero tonidero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

@AlvaroBrey
AlvaroBrey enabled auto-merge (squash) September 3, 2026 14:26
@AlvaroBrey
AlvaroBrey merged commit 51bcb72 into main Sep 3, 2026
12 checks passed
@AlvaroBrey
AlvaroBrey deleted the external/gabrieldonadel/agp9-built-in-kotlin branch September 3, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants