Skip to content

fix(android): build under AGP 9 built-in Kotlin and new DSL - #669

Merged
Shahroz16 merged 4 commits into
mainfrom
fix/android-agp9-dsl
Oct 23, 2026
Merged

Shahroz16 merged 4 commits into
mainfrom
fix/android-agp9-dsl

Conversation

@Shahroz16

@Shahroz16 Shahroz16 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Refs MBL-2258, MBL-2255

Stacked on #652 by @gabrieldonadel (Expo). Its commit is cherry-picked here with authorship kept and a Co-authored-by trailer, because the new CI job can't pass without it. Please review and merge #652 first. After it merges, rebase this branch onto main to drop the duplicate cherry-picked commit (a5988faf). This PR then reduces to the two commits below. Thanks, Gabriel, for the fix and the 500-library sweep behind it.

Why

React Native 0.87+ templates build on AGP 9 (RN 0.88.0: AGP 9.2.1, Gradle 9.4.1, Kotlin 2.2.0, which AGP resolves to 2.2.10). They still opt out of AGP 9's built-in Kotlin and new DSL (android.builtInKotlin=false, android.newDsl=false). AGP 10 removes the opt-out. An app that drops it today can't build with this SDK.

Changes

  1. fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension #652 (cherry-picked): apply kotlin-android only when AGP hasn't already registered the kotlin extension.

  2. android/build.gradle:

    • The two identical tasks.withType(KotlinCompile).all { kotlinOptions { … } } blocks become one kotlin { compilerOptions { optIn.addAll(…) } }. The old blocks also passed every flag twice. KGP 2.2 deprecates kotlinOptions at error level, and Kotlin warns that -Xopt-in is deprecated. The kotlin extension exists with both built-in Kotlin and kotlin-android. optIn is in KGP 2.0.21's KotlinCommonCompilerOptions.
    • lintOptions { disable … } → lint { disable.add('GradleCompatible') } (AGP 7+).
    • buildConfig true / compose true → =. These are the only two lines Gradle 9's problems report flags in this file (space-assignment is removed in Gradle 10).
  3. CI: scripts/build-android-template-host.sh + Android build compatibility workflow. Each run generates a stock RN template with @react-native-community/cli init, installs the npm packed SDK, runs :app:assembleDebug (x86_64) and asserts the module's Kotlin classes landed in its classes.jar. Matrix:

    • RN 0.77.3, module only (:customerio-reactnative:assembleDebug). This is the oldest React Native the module compiles on: AGP 8.7.2 / Gradle 8.10 / Kotlin 2.0.21. Its app as generated can't build with this SDK; see below.
    • RN 0.81.6, the oldest template whose app builds as generated (compileSdk 36, AGP 8.11.0, Kotlin 2.1.20)
    • RN 0.88.0, template as generated (AGP 9 with both opt-outs)
    • RN 0.88.0, AGP 9 defaults: android.builtInKotlin=true, android.newDsl=true, and the app's org.jetbrains.kotlin.android line removed

    The script fails if a template stops generating the lines a mode depends on. The module-class check reads the module's compile classes jar after an app build, or its debug AAR after a module-only build. It installs the template's NDK up front if missing (ubuntu-24.04 ships 27.3, the templates want 27.1.12297006). It prints the resolved AGP/KGP. Gradle caching uses setup-gradle with the MIT cache-provider: basic.

Local verification (macOS arm64, JDK 17)

All runs build a freshly generated template app with the npm packed SDK.

Host Mode SDK Result
RN 0.88.0: AGP 9.2.1, Gradle 9.4.1, KGP 2.2.0 → 2.2.10 AGP 9 defaults main ❌ Failed to apply plugin 'kotlin-android' › Cannot add extension with name 'kotlin'
RN 0.88.0 as generated main ✅
RN 0.88.0 AGP 9 defaults #652 ✅ :customerio-reactnative:compileDebugKotlin runs via built-in Kotlin; warns -Xopt-in is deprecated
RN 0.88.0 as generated #652 ✅
RN 0.88.0 AGP 9 defaults this PR ✅ No Kotlin argument warnings; module warnings identical to #652's
RN 0.88.0 as generated this PR ✅
RN 0.81.6: AGP 8.11.0, KGP 2.1.20 as generated this PR ✅
RN 0.77.3: AGP 8.7.2, KGP 2.0.21 module only this PR ✅ kotlin { compilerOptions { optIn } } and lint {} configure and compile
RN 0.77.3 as generated, app main ❌ checkDebugAarMetadata: androidx.core:core(-ktx):1.17.0 needs compileSdk 36 and AGP ≥ 8.9.1. Pre-existing since 5.1.0 (native Android 4.13.0); not caused by this PR.
RN 0.77.3 + compileSdk 36, AGP 8.9.1, Gradle 8.11.1 app main ✅
RN 0.76.9 as generated main ❌ Could not find org.jetbrains.kotlin:compose-compiler-gradle-plugin:1.9.25

Gradle 9's problems report on this module flagged only buildConfig true / compose true, now fixed.

#652 assessment

Safe to merge as-is. Merge it before this PR.

  • It fixes the AGP 9 defaults failure (table above) and leaves every other path unchanged. On AGP 8, and on AGP 9 with android.builtInKotlin=false, nothing registers kotlin before the check, so kotlin-android is applied exactly as today. Both RN 0.88 modes build with it, and its change is inside this PR's RN 0.77.3 and 0.81.6 builds.
  • Its own CI never fully ran because it comes from a fork. This PR's new workflow is the coverage it lacked.
  • On its own it leaves the kotlinOptions / -Xopt-in deprecations. Those still work from Groovy on KGP 2.2, but this PR removes them.
  • Edge case, which Codex also noted: the check is presence detection. A host that registers an unrelated kotlin extension (e.g. a JVM/KMP plugin applied to every subproject) would make the module skip kotlin-android. That's an unsupported setup, and no worse than today's hard failure.

Not changed here (follow-ups)

  • compileSdkVersion/minSdkVersion/targetSdkVersion methods still configure under AGP 9.2.1's new DSL, and Gradle doesn't flag them. Moving to compileSdk =/minSdk =, and deciding whether the library should keep a targetSdk at all, can be a separate cleanup.
  • The module adds kotlin-gradle-plugin and compose-compiler-gradle-plugin at the host's declared kotlinVersion to its own buildscript classpath. Under AGP 9 the root resolves KGP 2.2.10 while this asks for 2.2.0. Both modes build, but the module could rely on the root classpath instead.

Codex review

Approach consult (before writing):

  • Adopted: the kotlin { compilerOptions { optIn.addAll } } extension form; lint { disable.add() }; an oldest-RN job; a configurable CLI version; asserting template lines in both modes; absolute path normalization; installing the NDK up front; printing resolved AGP/KGP; setup-gradle with cache-provider: basic; keeping the generated host and uploading reports on failure.
  • Rejected: removing the library's targetSdkVersion. Codex said AGP 9's new DSL lacks it, but AGP 9.2.1 with newDsl=true configures it fine (table above), and Codex withdrew the claim in its review.
  • Deferred to follow-ups: compileSdk = / minSdk = assignments, and the buildscript KGP/Compose versions.

Adversarial review of the diff:

  • P3: the PR path filter missed src/postInstall.js (runs during both installs) and react-native.config.*. Fixed. The app module's report directory was also added to the failure artifact.
  • Found fine: opt-ins still reach main, unit-test and androidTest compilations through the extension; kotlin.RequiresOptIn is redundant but harmless; Groovy validity on AGP 8.7 / KGP 2.0.21 (now also confirmed by the 0.77.3 module build); no consumer-visible AAR or manifest change intended; set -euo pipefail usage; BSD/GNU sed/grep portability; the ubuntu-24.04 sdkmanager path; CLI 15/20 init flags; the stacking approach.

Re-review after the matrix changed (stock RN 0.77.3 apps can't build with this SDK, so the 0.77.3 job became module-only and RN 0.81.6 was added): no defect. Codex found the AAR fallback sound under set -euo pipefail. It also confirmed the module-only job guards plugin configuration, codegen and module compilation on the oldest RN/Kotlin, but not app packaging; the matrix comment says so.

🤖 Generated with Claude Code

gabrieldonadel and others added 3 commits October 20, 2026 12:48
…in extension

AGP 9 ships built-in Kotlin support and registers the kotlin extension
itself. Applying the Kotlin plugin again fails configuration with
"Cannot add extension with name 'kotlin'". Check for the extension
directly, which needs no AGP version table and covers AGP 10, where the
android.builtInKotlin opt-out is removed.

(cherry picked from commit 278eba6)
Co-authored-by: Gabriel Donadel Dall'Agnol <donadeldev@gmail.com>
KGP 2.2 deprecates `kotlinOptions` at error level, and AGP 9 with
android.newDsl=true is the default React Native will have to adopt (AGP 10
removes the opt-out). Under the new DSL the module still configured, but:

- replace the two identical `tasks.withType(KotlinCompile).all { kotlinOptions }`
  blocks (which also added every flag twice) with one
  `kotlin { compilerOptions { optIn.addAll(...) } }`. The `kotlin` extension
  exists both with AGP's built-in Kotlin and with kotlin-android, and the
  `-Xopt-in` flags it replaces are deprecated.
- `lintOptions` -> `lint { disable.add(...) }` (available since AGP 7).
- `buildConfig true` / `compose true` -> `=` assignments; Gradle 9 reports
  the space-assignment form as deprecated, to be removed in Gradle 10.

Refs MBL-2258

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add scripts/build-android-template-host.sh. It generates a React Native template
app, installs the packed SDK, runs a Gradle task (default :app:assembleDebug)
and checks that the module's Kotlin classes were compiled. `agp9-defaults`
turns on android.builtInKotlin and android.newDsl and drops the app's
kotlin-android, as AGP 9 intends; `template-defaults` builds the template as
generated.

The new "Android build compatibility" workflow runs:
- RN 0.77.3, module only. This is the oldest React Native the module compiles
  on (AGP 8.7, Kotlin 2.0.21). Its app as generated can't build with this SDK:
  AndroidX Core 1.17 from the native SDK needs compileSdk 36 and AGP 8.9.1.
- RN 0.81.6, the oldest template whose app builds as generated.
- RN 0.88.0 as generated (AGP 9 with both opt-outs), and with AGP 9 defaults.

Refs MBL-2255

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.


Builds are in progress. This comment will be updated when they finish.

  • iOS APN: 669.4.0 (29879743)
  • iOS FCM: 669.4.0 (29879743)

@Shahroz16
Shahroz16 merged commit 1068ea0 into main Oct 23, 2026
23 checks passed
@Shahroz16
Shahroz16 deleted the fix/android-agp9-dsl branch October 23, 2026 20:39
cio-mobile-release Bot pushed a commit that referenced this pull request Oct 23, 2026
## [6.16.1](6.16.0...6.16.1) (2026-10-23)

### Bug Fixes

* **android:** build under AGP 9 built-in Kotlin and new DSL ([#669](#669)) ([1068ea0](1068ea0))
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.

4 participants