Skip to content

fix(android): skip explicit kotlin-android plugin when AGP 9 built-in Kotlin is enabled - #1602

Merged
kirillzyusko merged 2 commits into
kirillzyusko:mainfrom
kimchi-developer:fix/agp-9-built-in-kotlin
Aug 14, 2026
Merged

fix(android): skip explicit kotlin-android plugin when AGP 9 built-in Kotlin is enabled#1602
kirillzyusko merged 2 commits into
kirillzyusko:mainfrom
kimchi-developer:fix/agp-9-built-in-kotlin

Conversation

@kimchi-developer

@kimchi-developer kimchi-developer commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📜 Description

android/build.gradle applies kotlin-android unconditionally. Since AGP 9.0 the Android Gradle Plugin provides Kotlin support itself, and applying the standalone Kotlin plugin on top of it makes configuration of this module fail.

This PR keeps apply plugin: 'kotlin-android' for every AGP version that needs it, and skips it only when AGP's built-in Kotlin is actually in play:

// AGP 9 ships built-in Kotlin support, which is on by default but can be disabled
// via the `android.builtInKotlin` Gradle property.
def hasBuiltInKotlinSupport() {
  def agpMajorVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
  def builtInKotlin = project.findProperty('android.builtInKotlin')

  return agpMajorVersion >= 9 && (builtInKotlin == null || builtInKotlin.toString().toBoolean())
}

apply plugin: 'com.android.library'
// applying `kotlin-android` on top of AGP's built-in Kotlin support fails the build
if (!hasBuiltInKotlinSupport()) {
  apply plugin: 'kotlin-android'
}

💡 Motivation and Context

(a) The symptom. On a project built with AGP 9, configuring this module fails with one of:

Failed to apply plugin 'org.jetbrains.kotlin.android'.
> Cannot add extension with name 'kotlin', as there is an extension already registered with that name.
Failed to apply plugin 'org.jetbrains.kotlin.android'
> The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0.

There is no consumer-side workaround other than pinning AGP 8 or globally disabling built-in Kotlin, so the fix has to live in the library.

(b) Why the condition has that shape. Built-in Kotlin is not simply "AGP >= 9" — it is a flag:

  • It exists only from AGP 9.0.
  • It is on by default in AGP 9.0.
  • A consumer can opt out with android.builtInKotlin=false in gradle.properties (documented as a temporary escape hatch that goes away in AGP 10).

So both halves are required. Dropping the property check would break every project that opted out — they need the explicit plugin. Dropping the version check would skip the plugin on AGP 8, where nothing else provides Kotlin. The builtInKotlin == null branch covers "property not set", which is the default-on case. References: Migrate to built-in Kotlin, AGP 9.0 release notes.

(c) Nothing changes on older AGP. For any AGP major below 9, hasBuiltInKotlinSupport() short-circuits to false and kotlin-android is applied exactly as before — same plugin, same position in the script, same kotlin_version resolution and kotlin-stdlib dependency.

com.android.Version is safe to read here: this file already uses com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION a few lines below to gate namespace / buildFeatures.buildConfig behind AGP 7+, and the call happens after apply plugin: 'com.android.library', so the AGP classes are on the classpath.

📢 Changelog

Android

  • Do not apply the standalone kotlin-android plugin when AGP 9's built-in Kotlin support is enabled, which unblocks building this library with AGP 9.

🤔 How Has This Been Tested?

  • Verified the modified android/build.gradle still parses as Groovy.

  • Verified the guard's evaluation directly, by running the same Groovy in a Gradle build script with the AGP version string supplied as a parameter, across 7.2.2 / 8.13.0 / 9.0.0 / 9.0.0-alpha01 / 10.1.2 and with android.builtInKotlin unset / true / false. Results match the truth table below; qualifier versions such as 9.0.0-alpha01 parse correctly because only the first .-separated token is read.

  • The hasBuiltInKotlinSupport() truth table:

    AGP android.builtInKotlin kotlin-android applied
    8.x unset / true / false ✅ yes (unchanged)
    9.x unset ❌ no (AGP provides Kotlin)
    9.x true ❌ no (AGP provides Kotlin)
    9.x false ✅ yes (consumer opted out)
  • I did not run the full android/ Gradle build against AGP 9 in CI here — the repository's Android jobs currently build against the pinned REACT_NATIVE_VERSION / AGP of the example app, so this path is not exercised by CI. Happy to add an AGP 9 build matrix entry if you'd like it in the same PR.

📸 Screenshots (if appropriate):

Not applicable — build script only.

📝 Checklist

  • CI successfully passed
  • I added new mocks and corresponding unit-tests if library API was changed

…in Kotlin is enabled

Since AGP 9.0 Kotlin support is built into the Android Gradle Plugin and is
enabled by default. When a library still applies `kotlin-android` on top of
it, the build fails while configuring this module:

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

Apply `kotlin-android` only when AGP's built-in Kotlin is not in play, i.e.
when the AGP major version is below 9, or when the consumer explicitly opted
out via `android.builtInKotlin=false`.

On AGP 8 and older the condition is always false, so the plugin is applied
exactly as before. `com.android.Version` is already used a few lines below
to detect AGP 7+, so it is safe to read here too.
Comment thread android/build.gradle Outdated
Comment on lines +38 to +45
// AGP 9 ships built-in Kotlin support, which is on by default but can be disabled
// via the `android.builtInKotlin` Gradle property.
def hasBuiltInKotlinSupport() {
def agpMajorVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
def builtInKotlin = project.findProperty('android.builtInKotlin')

return agpMajorVersion >= 9 && (builtInKotlin == null || builtInKotlin.toString().toBoolean())
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved it to react-native-helpers.gradle as project.ext.hasBuiltInKotlinSupport() 👍

One knock-on change: apply from: "$projectDir/react-native-helpers.gradle" used to sit after the plugin block, so the helper was not defined yet at the point where the decision is made. It now runs right after com.android.library. The helper script only defines project.ext closures and resolves the RN directory, so evaluating it a couple of lines earlier has no other effect.

@kimchi-developer kimchi-developer Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up: my first push of this failed CI with

Script '.../android/react-native-helpers.gradle' line: 51
> Could not get unknown property 'com' for root project 'android'

com.android.Version is on the buildscript classpath of build.gradle, and a script applied with apply from: does not inherit that, so the reference silently degrades to a property lookup on the project. Fixed by passing the version string into the helper instead:

if (!project.ext.hasBuiltInKotlinSupport(com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION)) {
  apply plugin: 'kotlin-android'
}

Verified the classpath behaviour and the truth table (AGP 9 → true, AGP 8 and 4.2.2 → false, android.builtInKotlin=false → false) on a minimal reproduction before pushing. Just pushed, so CI is re-running — happy to switch to reflection via project.buildscript.classLoader if you would rather keep the call site argument-free.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

📊 Package size report

Current size Target Size Difference
337039 bytes 336738 bytes 301 bytes 📈

@kirillzyusko kirillzyusko self-assigned this Aug 14, 2026
@kirillzyusko kirillzyusko added the 🤖 android Android specific label Aug 14, 2026
@kimchi-developer
kimchi-developer force-pushed the fix/agp-9-built-in-kotlin branch from 555cb70 to 81846de Compare August 14, 2026 10:47
…lpers.gradle`

Per review: keep the built-in Kotlin logic next to the other shared gradle
helpers instead of inlining it in `build.gradle`.

Two knock-on details:

* `apply from: react-native-helpers.gradle` now runs before the plugin block,
  so `project.ext.hasBuiltInKotlinSupport()` is defined when the decision is
  made. The helper script only defines `project.ext` closures, so evaluating
  it earlier has no other effect.
* The AGP version is passed in rather than read inside the helper.
  `com.android.Version` sits on the buildscript classpath of `build.gradle`,
  which scripts applied via `apply from:` do not inherit, so reading it there
  fails with "Could not get unknown property 'com'".
@kimchi-developer
kimchi-developer force-pushed the fix/agp-9-built-in-kotlin branch from 81846de to 0972d17 Compare August 14, 2026 10:51
@github-actions

Copy link
Copy Markdown
Contributor
  1. Incorrect evaluation of android.builtInKotlin property
    Why: The hasBuiltInKotlinSupport method incorrectly converts the string property value to a boolean, which can lead to unexpected results (e.g., "false" becomes true).
    Fix: Parse the property correctly, considering it as a boolean from the start or using Boolean.valueOf().

  2. Potential plugin application issue
    Why: The conditional application of the 'kotlin-android' plugin may fail if hasBuiltInKotlinSupport incorrectly determines the built-in Kotlin status, leading to missing or duplicate plugin configurations.
    Fix: Ensure accurate evaluation of hasBuiltInKotlinSupport by correctly interpreting the android.builtInKotlin property.

@kirillzyusko
kirillzyusko self-requested a review August 14, 2026 11:23
@kirillzyusko
kirillzyusko merged commit ddd20fb into kirillzyusko:main Aug 14, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Android specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants