From be41ec32c07d80d377955000507428c2d1353362 Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Wed, 2 Sep 2026 14:02:48 -0700 Subject: [PATCH 1/2] fix(android): skip explicit Kotlin plugin when AGP provides built-in Kotlin AGP 9 enables built-in Kotlin by default and applies the Kotlin plugin itself. Applying it again fails configuration with "Cannot add extension with name 'kotlin'". Guard the explicit apply so it only runs when AGP is not providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. AGP 10 removes that opt-out, so built-in Kotlin is always active there and the explicit apply must never run. --- .../android/build.gradle | 23 ++++++++++++++++++- .../android/build.gradle | 23 ++++++++++++++++++- .../android/build.gradle | 23 ++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/react-native-nitro-modules/android/build.gradle b/packages/react-native-nitro-modules/android/build.gradle index f564aad07..9011958ac 100644 --- a/packages/react-native-nitro-modules/android/build.gradle +++ b/packages/react-native-nitro-modules/android/build.gradle @@ -21,7 +21,28 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -apply plugin: 'org.jetbrains.kotlin.android' +// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin +// plugin itself. Applying it a second time fails the configuration phase with +// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not +// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. +def shouldApplyKotlinPlugin() { + def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() + if (agpMajor <= 8) { + return true + } + // AGP 10 removes the opt-out: built-in Kotlin is always active there. + if (agpMajor >= 10) { + return false + } + def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull + def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true + return !builtInKotlinEnabled +} + +if (shouldApplyKotlinPlugin()) { + apply plugin: 'org.jetbrains.kotlin.android' +} + apply from: "./fix-prefab.gradle" if (isNewArchitectureEnabled()) { diff --git a/packages/react-native-nitro-test-external/android/build.gradle b/packages/react-native-nitro-test-external/android/build.gradle index 32a5e3bdd..98b0e9970 100644 --- a/packages/react-native-nitro-test-external/android/build.gradle +++ b/packages/react-native-nitro-test-external/android/build.gradle @@ -19,7 +19,28 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -apply plugin: 'org.jetbrains.kotlin.android' +// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin +// plugin itself. Applying it a second time fails the configuration phase with +// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not +// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. +def shouldApplyKotlinPlugin() { + def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() + if (agpMajor <= 8) { + return true + } + // AGP 10 removes the opt-out: built-in Kotlin is always active there. + if (agpMajor >= 10) { + return false + } + def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull + def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true + return !builtInKotlinEnabled +} + +if (shouldApplyKotlinPlugin()) { + apply plugin: 'org.jetbrains.kotlin.android' +} + apply from: '../nitrogen/generated/android/NitroTestExternal+autolinking.gradle' apply from: "./fix-prefab.gradle" diff --git a/packages/react-native-nitro-test/android/build.gradle b/packages/react-native-nitro-test/android/build.gradle index 030feaafc..281e76d4e 100644 --- a/packages/react-native-nitro-test/android/build.gradle +++ b/packages/react-native-nitro-test/android/build.gradle @@ -19,7 +19,28 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -apply plugin: 'org.jetbrains.kotlin.android' +// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin +// plugin itself. Applying it a second time fails the configuration phase with +// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not +// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. +def shouldApplyKotlinPlugin() { + def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() + if (agpMajor <= 8) { + return true + } + // AGP 10 removes the opt-out: built-in Kotlin is always active there. + if (agpMajor >= 10) { + return false + } + def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull + def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true + return !builtInKotlinEnabled +} + +if (shouldApplyKotlinPlugin()) { + apply plugin: 'org.jetbrains.kotlin.android' +} + apply from: '../nitrogen/generated/android/NitroTest+autolinking.gradle' if (isNewArchitectureEnabled()) { From 0f83cb24bf177fe9f919afccfc687292b0d9a193 Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Fri, 18 Sep 2026 03:21:35 -0700 Subject: [PATCH 2/2] fix(android): check the kotlin extension instead of the AGP version Replace the AGP version / android.builtInKotlin check with a direct test for the registered kotlin extension. The version check reads com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION, which can resolve to a different classpath entry than the AGP actually in use, and the global android.builtInKotlin property can be overridden per module by the com.android.built-in-kotlin plugin -- so both inputs can disagree with reality. Asking whether the kotlin extension exists tests the condition that actually fails, needs no AGP version table, and covers AGP 10 where the opt-out is removed. --- .../android/build.gradle | 26 +++++-------------- .../android/build.gradle | 26 +++++-------------- .../android/build.gradle | 26 +++++-------------- 3 files changed, 21 insertions(+), 57 deletions(-) diff --git a/packages/react-native-nitro-modules/android/build.gradle b/packages/react-native-nitro-modules/android/build.gradle index 9011958ac..9e59a6833 100644 --- a/packages/react-native-nitro-modules/android/build.gradle +++ b/packages/react-native-nitro-modules/android/build.gradle @@ -21,25 +21,13 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin -// plugin itself. Applying it a second time fails the configuration phase with -// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not -// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. -def shouldApplyKotlinPlugin() { - def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() - if (agpMajor <= 8) { - return true - } - // AGP 10 removes the opt-out: built-in Kotlin is always active there. - if (agpMajor >= 10) { - return false - } - def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull - def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true - return !builtInKotlinEnabled -} - -if (shouldApplyKotlinPlugin()) { +// AGP 9 ships built-in Kotlin support and registers the `kotlin` extension +// itself. Applying the Kotlin plugin on top of it fails configuration with +// "Cannot add extension with name 'kotlin'". Asking for the extension covers +// every way built-in Kotlin can be turned on -- the global default, the +// android.builtInKotlin property, and the per-module com.android.built-in-kotlin +// plugin -- without reading an AGP version number. +if (project.extensions.findByName('kotlin') == null) { apply plugin: 'org.jetbrains.kotlin.android' } diff --git a/packages/react-native-nitro-test-external/android/build.gradle b/packages/react-native-nitro-test-external/android/build.gradle index 98b0e9970..89e4f084a 100644 --- a/packages/react-native-nitro-test-external/android/build.gradle +++ b/packages/react-native-nitro-test-external/android/build.gradle @@ -19,25 +19,13 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin -// plugin itself. Applying it a second time fails the configuration phase with -// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not -// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. -def shouldApplyKotlinPlugin() { - def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() - if (agpMajor <= 8) { - return true - } - // AGP 10 removes the opt-out: built-in Kotlin is always active there. - if (agpMajor >= 10) { - return false - } - def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull - def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true - return !builtInKotlinEnabled -} - -if (shouldApplyKotlinPlugin()) { +// AGP 9 ships built-in Kotlin support and registers the `kotlin` extension +// itself. Applying the Kotlin plugin on top of it fails configuration with +// "Cannot add extension with name 'kotlin'". Asking for the extension covers +// every way built-in Kotlin can be turned on -- the global default, the +// android.builtInKotlin property, and the per-module com.android.built-in-kotlin +// plugin -- without reading an AGP version number. +if (project.extensions.findByName('kotlin') == null) { apply plugin: 'org.jetbrains.kotlin.android' } diff --git a/packages/react-native-nitro-test/android/build.gradle b/packages/react-native-nitro-test/android/build.gradle index 281e76d4e..b9174482f 100644 --- a/packages/react-native-nitro-test/android/build.gradle +++ b/packages/react-native-nitro-test/android/build.gradle @@ -19,25 +19,13 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin -// plugin itself. Applying it a second time fails the configuration phase with -// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not -// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false. -def shouldApplyKotlinPlugin() { - def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger() - if (agpMajor <= 8) { - return true - } - // AGP 10 removes the opt-out: built-in Kotlin is always active there. - if (agpMajor >= 10) { - return false - } - def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull - def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true - return !builtInKotlinEnabled -} - -if (shouldApplyKotlinPlugin()) { +// AGP 9 ships built-in Kotlin support and registers the `kotlin` extension +// itself. Applying the Kotlin plugin on top of it fails configuration with +// "Cannot add extension with name 'kotlin'". Asking for the extension covers +// every way built-in Kotlin can be turned on -- the global default, the +// android.builtInKotlin property, and the per-module com.android.built-in-kotlin +// plugin -- without reading an AGP version number. +if (project.extensions.findByName('kotlin') == null) { apply plugin: 'org.jetbrains.kotlin.android' }