From 0e811daf423ccb53fb4cc353e5077e4f9a1be85b Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 09:00:41 +0200 Subject: [PATCH 01/13] Apply kotlin-android conditionally for AGP 9 compatibility The main plugin's Android sources are Java-only, so KGP is dropped entirely there. The UI plugin still needs Kotlin, so kotlin-android is now applied only when AGP < 9 (AGP 9+ provides built-in Kotlin via KGP 2.2.10+). Because consumers on Flutter 3.22 may still pin KGP 1.x, both the legacy android.kotlinOptions and the new top-level kotlin.compilerOptions DSLs are gated on the same AGP version check. Resolves #1759. Co-authored-by: Cursor --- android/build.gradle | 8 ------ purchases_ui_flutter/android/build.gradle | 33 +++++++++++++++++++---- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 33c0374e9..ec580446e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,6 @@ group 'com.revenuecat.purchases_flutter' version '10.2.0' buildscript { - ext.kotlin_version = '1.8.22' ext.common_version = '18.9.1' repositories { google() @@ -11,7 +10,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.13.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -23,7 +21,6 @@ rootProject.allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' android { compileSdk 34 @@ -38,10 +35,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - defaultConfig { minSdkVersion 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -64,6 +57,5 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.revenuecat.purchases:purchases-hybrid-common:$common_version" } diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index e8f3fc99d..f88eabc34 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -23,7 +23,14 @@ allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' + +// AGP 9+ provides built-in Kotlin support, so we should only apply the +// kotlin-android plugin on older AGP versions. See: +// https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} android { if (project.android.hasProperty("namespace")) { @@ -35,10 +42,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -50,9 +53,29 @@ android { missingDimensionStrategy 'apis', 'defaults' missingDimensionStrategy 'billingclient', 'bc8' } + + // On AGP < 9 we apply kotlin-android ourselves, which exposes the legacy + // android.kotlinOptions DSL. On AGP 9+ kotlin-android is not applied and + // the new top-level kotlin.compilerOptions DSL is used instead (below). + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = '1.8' + } + } dependencies { implementation "com.revenuecat.purchases:purchases-hybrid-common-ui:$common_version" implementation 'androidx.compose.ui:ui-android:1.5.4' } } + +// AGP 9+ ships with built-in Kotlin (KGP 2.2.10+), which exposes the new +// top-level kotlin.compilerOptions DSL. The legacy android.kotlinOptions +// block above does not exist in this configuration. +if (agpMajor >= 9) { + kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 + } + } +} From 81d705afe593df2647ac3e06e49e9ee6f542fe1f Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 11:12:57 +0200 Subject: [PATCH 02/13] Consolidate AGP-version DSL branches into a single if-else No functional change: configures kotlinOptions / kotlin.compilerOptions in one place instead of two separate conditional blocks. Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index f88eabc34..60c864acc 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -53,15 +53,6 @@ android { missingDimensionStrategy 'apis', 'defaults' missingDimensionStrategy 'billingclient', 'bc8' } - - // On AGP < 9 we apply kotlin-android ourselves, which exposes the legacy - // android.kotlinOptions DSL. On AGP 9+ kotlin-android is not applied and - // the new top-level kotlin.compilerOptions DSL is used instead (below). - if (agpMajor < 9) { - kotlinOptions { - jvmTarget = '1.8' - } - } dependencies { implementation "com.revenuecat.purchases:purchases-hybrid-common-ui:$common_version" @@ -69,10 +60,16 @@ android { } } -// AGP 9+ ships with built-in Kotlin (KGP 2.2.10+), which exposes the new -// top-level kotlin.compilerOptions DSL. The legacy android.kotlinOptions -// block above does not exist in this configuration. -if (agpMajor >= 9) { +// AGP < 9: we apply kotlin-android ourselves (above), which exposes the +// legacy android.kotlinOptions DSL. AGP 9+ provides built-in Kotlin +// (KGP 2.2.10+) and the new top-level kotlin.compilerOptions DSL instead. +if (agpMajor < 9) { + android { + kotlinOptions { + jvmTarget = '1.8' + } + } +} else { kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 From c6fd2b61fd656e64e8d5c47d1f9ebf45db5dc5af Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 11:18:56 +0200 Subject: [PATCH 03/13] Gate kotlin-gradle-plugin classpath on AGP < 9 Under AGP 9+ Kotlin is provided by built-in Kotlin (KGP 2.2.10+), so the kotlin-gradle-plugin classpath we declare for AGP < 9 is redundant and could in theory cause classpath conflicts. Gate it on the same agpMajor check as the plugin application. agpMajor is now defined as ext.agpMajor inside the buildscript block (rather than at the script top level) because Gradle processes the buildscript block before the rest of the script body. Defining it via ext makes it accessible everywhere. Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index 60c864acc..27d7017bf 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -1,9 +1,14 @@ group 'com.revenuecat.purchases_ui_flutter' version '10.2.0' +// AGP 9+ provides built-in Kotlin support, so we should only apply the +// kotlin-android plugin (and its associated buildscript classpath) on +// older AGP versions. See: +// https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors buildscript { ext.kotlin_version = '1.9.20' ext.common_version = '18.9.1' + ext.agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int repositories { google() mavenCentral() @@ -11,7 +16,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.13.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + if (agpMajor < 9) { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } } @@ -24,10 +31,6 @@ allprojects { apply plugin: 'com.android.library' -// AGP 9+ provides built-in Kotlin support, so we should only apply the -// kotlin-android plugin on older AGP versions. See: -// https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors -def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int if (agpMajor < 9) { apply plugin: 'kotlin-android' } From 883232e7ffc6412e42c8676a532c9a02032e75a3 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 11:32:59 +0200 Subject: [PATCH 04/13] Simplify AGP-version DSL comment KGP 2.0+ is the actual floor required for the top-level kotlin.compilerOptions DSL (per the Flutter migration guide), and matches what's documented. The previous "2.2.10+" was an implementation detail of what AGP 9.0 currently bundles, which isn't a useful version to pin in a comment. Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index 27d7017bf..903d8ea24 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -63,9 +63,10 @@ android { } } -// AGP < 9: we apply kotlin-android ourselves (above), which exposes the -// legacy android.kotlinOptions DSL. AGP 9+ provides built-in Kotlin -// (KGP 2.2.10+) and the new top-level kotlin.compilerOptions DSL instead. +// On AGP 9+ the built-in Kotlin plugin (KGP 2.0+) is the only one +// available; configure jvmTarget via the new top-level DSL. On AGP < 9 +// the kotlin-android plugin is applied (above) and exposes the legacy +// android.kotlinOptions DSL. if (agpMajor < 9) { android { kotlinOptions { From 783ce20e2db171b7043a2a3056231b43383e4554 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 11:45:21 +0200 Subject: [PATCH 05/13] Add AGP 9 smoke test for purchases_ui_flutter The plugin's android/build.gradle has an AGP-version conditional that switches to a new top-level kotlin.compilerOptions DSL when running under AGP 9 (which provides Kotlin built-in via KGP 2.x). That branch is not exercised by any existing build, because the example apps and CI all run under AGP 8 + KGP 1.x and the Flutter ecosystem isn't yet fully AGP 9-ready end to end. This adds a small Gradle-only fixture under .circleci/agp9-smoke/ that pins AGP 9.0.1 and KGP 2.2.10 via pluginManagement and runs the plugin in isolation. It's a configuration-time smoke test: it asserts that agpMajor evaluates to 9 (so the AGP-9 branch ran) and that the compileDebugKotlin task graph resolves under --dry-run (so AGP applied cleanly and the new DSL is valid). It does not run a full build. A new agp9-smoke-test CircleCI job wires this into the test workflow. Co-authored-by: Cursor --- .circleci/agp9-smoke/.gitignore | 2 ++ .circleci/agp9-smoke/README.md | 51 ++++++++++++++++++++++++++++ .circleci/agp9-smoke/run.sh | 36 ++++++++++++++++++++ .circleci/agp9-smoke/settings.gradle | 30 ++++++++++++++++ .circleci/config.yml | 35 +++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 .circleci/agp9-smoke/.gitignore create mode 100644 .circleci/agp9-smoke/README.md create mode 100755 .circleci/agp9-smoke/run.sh create mode 100644 .circleci/agp9-smoke/settings.gradle diff --git a/.circleci/agp9-smoke/.gitignore b/.circleci/agp9-smoke/.gitignore new file mode 100644 index 000000000..67bcc2f72 --- /dev/null +++ b/.circleci/agp9-smoke/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build/ diff --git a/.circleci/agp9-smoke/README.md b/.circleci/agp9-smoke/README.md new file mode 100644 index 000000000..062328dd1 --- /dev/null +++ b/.circleci/agp9-smoke/README.md @@ -0,0 +1,51 @@ +# AGP 9 smoke test for `purchases_ui_flutter` + +A minimal Gradle-only fixture that exercises the AGP-9 branch of +`purchases_ui_flutter/android/build.gradle`, without needing Flutter +or a full app build. + +## What it tests + +`purchases_ui_flutter/android/build.gradle` switches behaviour based on the +detected Android Gradle Plugin major version: + +- `agpMajor < 9` — applies the `kotlin-android` plugin and uses the + legacy `android.kotlinOptions { … }` DSL. +- `agpMajor >= 9` — relies on AGP's built-in Kotlin and uses the + top-level `kotlin { compilerOptions { … } }` DSL. + +The first branch is exercised by every regular CI build (the +`purchase_tester` example pins AGP 8 / KGP 1.x). + +The AGP 9 branch only runs when a downstream user is on a recent enough +Flutter (3.44+) and AGP 9, which our existing CI doesn't cover. This +fixture lets CI catch regressions on that branch (typos, wrong DSL +references, etc.) at configuration time. + +## How it works + +`settings.gradle` pins AGP `9.0.1` and KGP `2.2.10` via +`pluginManagement`, then includes the plugin's `android/` directory as +a subproject. The plugin's own `buildscript { }` declares a lower AGP +version; Gradle resolves the classpath to the highest declared version, +so the plugin's `build.gradle` is evaluated under AGP 9 here. + +`run.sh` asserts: + +1. `agpMajor == 9` after the plugin's `buildscript { }` runs — proves + the AGP-9 branches of the plugin's `build.gradle` are reached. +2. The `compileDebugKotlin` task graph resolves under `--dry-run` — + proves AGP applied cleanly and the new top-level + `kotlin.compilerOptions` DSL is valid. + +This is a configuration-time smoke test, not a full build. It will not +catch issues that only manifest during actual compilation (e.g. Kotlin +source incompatibilities with KGP 2.x). + +## Running locally + +Requires JDK 21, Gradle 9.1+, and an Android SDK installed. + +```bash +bash .circleci/agp9-smoke/run.sh +``` diff --git a/.circleci/agp9-smoke/run.sh b/.circleci/agp9-smoke/run.sh new file mode 100755 index 000000000..bf5d98663 --- /dev/null +++ b/.circleci/agp9-smoke/run.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Smoke-test purchases_ui_flutter/android/build.gradle under AGP 9. +# +# Uses the sibling settings.gradle, which pins AGP 9.0.1 + KGP 2.2.10 +# via pluginManagement. The plugin's own buildscript declares a lower +# AGP version, but Gradle resolves the classpath to the highest, so +# the plugin's build.gradle is evaluated with AGP 9 here. +# +# Assertions: +# 1. agpMajor == 9 (so the AGP 9 else-branch in build.gradle ran). +# 2. compileDebugKotlin task graph resolves (built-in Kotlin works). +# +# This is a configuration-time smoke test, not a full build. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +GRADLE_ARGS=(--no-daemon -q) + +echo "==> Asserting agpMajor == 9" +agp_major="$(gradle "${GRADLE_ARGS[@]}" :purchases_ui_flutter:properties | awk '/^agpMajor: / {print $2}')" +if [[ "$agp_major" != "9" ]]; then + echo "FAIL: expected agpMajor=9 but got '${agp_major}'." >&2 + echo " The AGP 9 branch of purchases_ui_flutter/android/build.gradle was not exercised." >&2 + exit 1 +fi +echo "OK: agpMajor=${agp_major}" + +echo "==> Asserting compileDebugKotlin task graph resolves under AGP 9" +gradle "${GRADLE_ARGS[@]}" :purchases_ui_flutter:compileDebugKotlin --dry-run +echo "OK: AGP 9 + built-in Kotlin wired up correctly" + +echo "==> AGP 9 smoke test passed" diff --git a/.circleci/agp9-smoke/settings.gradle b/.circleci/agp9-smoke/settings.gradle new file mode 100644 index 000000000..97c87d348 --- /dev/null +++ b/.circleci/agp9-smoke/settings.gradle @@ -0,0 +1,30 @@ +// Settings file for the AGP 9 smoke test of purchases_ui_flutter. +// See README.md in this directory for context. + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +// Pin the AGP and Kotlin Gradle Plugin versions used during this smoke +// test. The plugin's own buildscript declares a lower AGP version; Gradle +// resolves the buildscript classpath to the highest declared version, so +// the plugin gets evaluated with AGP 9 here. +plugins { + id "com.android.application" version "9.0.1" apply false + id "org.jetbrains.kotlin.android" version "2.2.10" apply false +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "agp9-smoke" +include ":purchases_ui_flutter" +project(":purchases_ui_flutter").projectDir = file("../../purchases_ui_flutter/android") diff --git a/.circleci/config.yml b/.circleci/config.yml index fc6071cde..2a31bf3be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -324,6 +324,40 @@ jobs: ./gradlew app:assembleAndroidTest ./gradlew app:assembleDebug -Ptarget=`pwd`/../integration_test/app_test.dart + agp9-smoke-test: + description: "Smoke-test purchases_ui_flutter under AGP 9 (configuration-time only)" + docker: + - image: ghcr.io/cirruslabs/flutter:stable + resource_class: medium + steps: + - checkout + - run: + name: Install JDK 21 (required by AGP 9) + command: | + curl -fsSL https://get.sdkman.io | bash + set +u + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk install java 21.0.7-librca + JAVA_HOME_21="$(sdk home java 21.0.7-librca)" + { + echo "export JAVA_HOME=$JAVA_HOME_21" + echo "export PATH=$JAVA_HOME_21/bin:\$PATH" + } >> "$BASH_ENV" + - run: + name: Install Gradle 9.1.0 (required by AGP 9) + command: | + curl -fsLO https://services.gradle.org/distributions/gradle-9.1.0-bin.zip + unzip -q gradle-9.1.0-bin.zip -d /opt + echo "export PATH=/opt/gradle-9.1.0/bin:\$PATH" >> "$BASH_ENV" + - run: + name: Print versions + command: | + java -version + gradle --version + - run: + name: Run AGP 9 smoke test + command: bash .circleci/agp9-smoke/run.sh + android-integration-test: description: "Run Android integration tests for flutter" <<: *android-machine-emulator @@ -589,6 +623,7 @@ workflows: - ios-integration-test-spm - macos-integration-test-spm - android-integration-test-build + - agp9-smoke-test - run-maestro-e2e-tests-ios: context: - e2e-tests From 22934a0d5e18c6ef5b5674d2ddc29d9b918eb24a Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 12:05:25 +0200 Subject: [PATCH 06/13] Trim AGP 9 smoke test README Drop redundant qualifiers; the surrounding sentences already explain what the smoke test does and doesn't cover. Co-authored-by: Cursor --- .circleci/agp9-smoke/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/agp9-smoke/README.md b/.circleci/agp9-smoke/README.md index 062328dd1..dbc980b23 100644 --- a/.circleci/agp9-smoke/README.md +++ b/.circleci/agp9-smoke/README.md @@ -18,9 +18,8 @@ The first branch is exercised by every regular CI build (the `purchase_tester` example pins AGP 8 / KGP 1.x). The AGP 9 branch only runs when a downstream user is on a recent enough -Flutter (3.44+) and AGP 9, which our existing CI doesn't cover. This -fixture lets CI catch regressions on that branch (typos, wrong DSL -references, etc.) at configuration time. +Flutter (3.44+) and AGP 9. This fixture lets CI catch regressions on +that branch at configuration time. ## How it works From 5db2df89632b68af0b74485f80d2ce8043f7e075 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 12:05:35 +0200 Subject: [PATCH 07/13] Install JDK 21 and Gradle 9.1 via SDKMAN in agp9-smoke-test The previous version of the job installed SDKMAN with an unpinned 'curl | bash' and downloaded a Gradle distribution from gradle.org inline. Both of those are now handled by the shared infrastructure: - revenuecat/install-sdkman from the shared orb installs SDKMAN with a pinned, SHA-verified release artifact. - A local .sdkmanrc next to the smoke test pins both candidates (java=21.0.7-librca, gradle=9.1.0). `sdk env install` reads it. The local `install-sdkman` command in config.yml is bypassed because it auto-runs `sdk env install` against the project root's .sdkmanrc, which pins JDK 17. Co-authored-by: Cursor --- .circleci/agp9-smoke/.sdkmanrc | 6 ++++++ .circleci/config.yml | 22 ++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 .circleci/agp9-smoke/.sdkmanrc diff --git a/.circleci/agp9-smoke/.sdkmanrc b/.circleci/agp9-smoke/.sdkmanrc new file mode 100644 index 000000000..06021bdfb --- /dev/null +++ b/.circleci/agp9-smoke/.sdkmanrc @@ -0,0 +1,6 @@ +# JDK 21 and Gradle 9.1 are required by AGP 9. +# This .sdkmanrc is consumed by `sdk env install` in the agp9-smoke-test +# CircleCI job. The project root pins JDK 17 (used by every other job); +# only this smoke test needs JDK 21 + a newer Gradle. +java=21.0.7-librca +gradle=9.1.0 diff --git a/.circleci/config.yml b/.circleci/config.yml index 2a31bf3be..85f23178a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -331,24 +331,22 @@ jobs: resource_class: medium steps: - checkout + # Use the orb's installer directly (not the local `install-sdkman` + # command) because the local one auto-runs `sdk env install` against + # the root .sdkmanrc, which pins JDK 17. This job needs JDK 21 + # and Gradle 9.1, both declared in .circleci/agp9-smoke/.sdkmanrc. + - revenuecat/install-sdkman - run: - name: Install JDK 21 (required by AGP 9) + name: Install JDK 21 and Gradle 9.1 (required by AGP 9) + working_directory: .circleci/agp9-smoke command: | - curl -fsSL https://get.sdkman.io | bash - set +u - source "$HOME/.sdkman/bin/sdkman-init.sh" - sdk install java 21.0.7-librca + sdk env install JAVA_HOME_21="$(sdk home java 21.0.7-librca)" + GRADLE_91_HOME="$(sdk home gradle 9.1.0)" { echo "export JAVA_HOME=$JAVA_HOME_21" - echo "export PATH=$JAVA_HOME_21/bin:\$PATH" + echo "export PATH=$JAVA_HOME_21/bin:$GRADLE_91_HOME/bin:\$PATH" } >> "$BASH_ENV" - - run: - name: Install Gradle 9.1.0 (required by AGP 9) - command: | - curl -fsLO https://services.gradle.org/distributions/gradle-9.1.0-bin.zip - unzip -q gradle-9.1.0-bin.zip -d /opt - echo "export PATH=/opt/gradle-9.1.0/bin:\$PATH" >> "$BASH_ENV" - run: name: Print versions command: | From bcf4a520f4ea89369dc729cecfbc0e3cc806452c Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 12:12:16 +0200 Subject: [PATCH 08/13] Read JDK and Gradle versions from .sdkmanrc in agp9-smoke-test The previous version of the install step pinned versions in both .circleci/agp9-smoke/.sdkmanrc (consumed by `sdk env install`) and the install command itself (used by `sdk home` to compute paths). Parse the versions out of .sdkmanrc instead, so the file is the single source of truth. Co-authored-by: Cursor --- .circleci/config.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 85f23178a..01a9dd2db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -341,11 +341,13 @@ jobs: working_directory: .circleci/agp9-smoke command: | sdk env install - JAVA_HOME_21="$(sdk home java 21.0.7-librca)" - GRADLE_91_HOME="$(sdk home gradle 9.1.0)" + JAVA_VERSION="$(awk -F= '$1=="java"{print $2}' .sdkmanrc)" + GRADLE_VERSION="$(awk -F= '$1=="gradle"{print $2}' .sdkmanrc)" + JAVA_DIR="$(sdk home java "$JAVA_VERSION")" + GRADLE_DIR="$(sdk home gradle "$GRADLE_VERSION")" { - echo "export JAVA_HOME=$JAVA_HOME_21" - echo "export PATH=$JAVA_HOME_21/bin:$GRADLE_91_HOME/bin:\$PATH" + echo "export JAVA_HOME=$JAVA_DIR" + echo "export PATH=$JAVA_DIR/bin:$GRADLE_DIR/bin:\$PATH" } >> "$BASH_ENV" - run: name: Print versions From 8e132f05c64367463b4ae2be631a3510f4c22868 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 12:32:24 +0200 Subject: [PATCH 09/13] Drop manual BASH_ENV writes in agp9-smoke-test The orb's install-sdkman step already sources sdkman-init.sh in $BASH_ENV for every subsequent step. sdkman-init.sh reads the candidate `current` symlinks at every shell startup and sets JAVA_HOME and PATH from them. In a fresh SDKMAN install, the first install of each candidate is auto-set as its default, so after `sdk env install` runs, the orb's existing $BASH_ENV plumbing takes care of activating the right versions in every later step. Co-authored-by: Cursor --- .circleci/config.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 01a9dd2db..9b3672b36 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -336,19 +336,15 @@ jobs: # the root .sdkmanrc, which pins JDK 17. This job needs JDK 21 # and Gradle 9.1, both declared in .circleci/agp9-smoke/.sdkmanrc. - revenuecat/install-sdkman + # In a fresh SDKMAN install (no prior candidates), the first install + # of each candidate is auto-set as its default. The orb's + # install-sdkman step already sources sdkman-init.sh in $BASH_ENV + # for every subsequent step, which picks JAVA_HOME and PATH from + # the candidate `current` symlinks. So no $BASH_ENV writes here. - run: name: Install JDK 21 and Gradle 9.1 (required by AGP 9) working_directory: .circleci/agp9-smoke - command: | - sdk env install - JAVA_VERSION="$(awk -F= '$1=="java"{print $2}' .sdkmanrc)" - GRADLE_VERSION="$(awk -F= '$1=="gradle"{print $2}' .sdkmanrc)" - JAVA_DIR="$(sdk home java "$JAVA_VERSION")" - GRADLE_DIR="$(sdk home gradle "$GRADLE_VERSION")" - { - echo "export JAVA_HOME=$JAVA_DIR" - echo "export PATH=$JAVA_DIR/bin:$GRADLE_DIR/bin:\$PATH" - } >> "$BASH_ENV" + command: sdk env install - run: name: Print versions command: | From e2b9cb3b05a74f134c8e1f58553569e313dd8856 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 29 May 2026 12:33:39 +0200 Subject: [PATCH 10/13] Drop explanatory comments in agp9-smoke-test job Co-authored-by: Cursor --- .circleci/config.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9b3672b36..e0e7bb668 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -331,16 +331,7 @@ jobs: resource_class: medium steps: - checkout - # Use the orb's installer directly (not the local `install-sdkman` - # command) because the local one auto-runs `sdk env install` against - # the root .sdkmanrc, which pins JDK 17. This job needs JDK 21 - # and Gradle 9.1, both declared in .circleci/agp9-smoke/.sdkmanrc. - revenuecat/install-sdkman - # In a fresh SDKMAN install (no prior candidates), the first install - # of each candidate is auto-set as its default. The orb's - # install-sdkman step already sources sdkman-init.sh in $BASH_ENV - # for every subsequent step, which picks JAVA_HOME and PATH from - # the candidate `current` symlinks. So no $BASH_ENV writes here. - run: name: Install JDK 21 and Gradle 9.1 (required by AGP 9) working_directory: .circleci/agp9-smoke From 5f08c323818d4b784da7dde0d44b02cbdfd1b227 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 1 Jun 2026 12:57:50 +0200 Subject: [PATCH 11/13] Also apply kotlin-android when builtInKotlin is opted out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier gating only checked AGP major version, which assumed that on AGP 9+ the kotlin {} extension is always registered (either by kotlin-android or by AGP's built-in Kotlin). That assumption breaks when the consumer sets android.builtInKotlin=false in gradle.properties — the default the Flutter migrator adds for Flutter 3.44+ apps. In that configuration neither path registers the kotlin {} extension, and our top-level kotlin { compilerOptions { ... } } block fails at configuration time with `Could not find method kotlin()`. Apply kotlin-android (and pull its classpath) whenever AGP won't provide Kotlin itself: AGP < 9, or AGP 9+ with built-in Kotlin opted out. Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index 903d8ea24..114d5d98e 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -1,14 +1,20 @@ group 'com.revenuecat.purchases_ui_flutter' version '10.2.0' -// AGP 9+ provides built-in Kotlin support, so we should only apply the -// kotlin-android plugin (and its associated buildscript classpath) on -// older AGP versions. See: +// AGP 9+ ships built-in Kotlin support and forbids applying the legacy +// kotlin-android plugin when it is enabled. AGP 9+ also lets consumers opt +// out via `android.builtInKotlin=false` in gradle.properties — which is in +// fact the default the Flutter migrator sets for Flutter 3.44+ apps. We +// therefore apply kotlin-android (and pull its classpath) whenever AGP +// won't provide Kotlin itself: AGP < 9, or AGP 9+ with built-in Kotlin +// opted out. See: // https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors buildscript { ext.kotlin_version = '1.9.20' ext.common_version = '18.9.1' ext.agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int + ext.builtInKotlinEnabled = (findProperty('android.builtInKotlin') ?: 'true') == 'true' + ext.applyLegacyKgp = agpMajor < 9 || !builtInKotlinEnabled repositories { google() mavenCentral() @@ -16,7 +22,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.13.2' - if (agpMajor < 9) { + if (applyLegacyKgp) { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -31,7 +37,7 @@ allprojects { apply plugin: 'com.android.library' -if (agpMajor < 9) { +if (applyLegacyKgp) { apply plugin: 'kotlin-android' } @@ -63,11 +69,10 @@ android { } } -// On AGP 9+ the built-in Kotlin plugin (KGP 2.0+) is the only one -// available; configure jvmTarget via the new top-level DSL. On AGP < 9 -// the kotlin-android plugin is applied (above) and exposes the legacy -// android.kotlinOptions DSL. -if (agpMajor < 9) { +// When we apply kotlin-android ourselves, configure jvmTarget through the +// legacy android.kotlinOptions DSL it exposes. Otherwise AGP 9+'s built-in +// Kotlin is in play and we configure it via the new top-level kotlin DSL. +if (applyLegacyKgp) { android { kotlinOptions { jvmTarget = '1.8' From 50fcbafc855032565ec2cd8320c82a7089d24de2 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 1 Jun 2026 13:10:08 +0200 Subject: [PATCH 12/13] Tighten AGP 9 gating comments Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index 114d5d98e..7441930fc 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -1,13 +1,9 @@ group 'com.revenuecat.purchases_ui_flutter' version '10.2.0' -// AGP 9+ ships built-in Kotlin support and forbids applying the legacy -// kotlin-android plugin when it is enabled. AGP 9+ also lets consumers opt -// out via `android.builtInKotlin=false` in gradle.properties — which is in -// fact the default the Flutter migrator sets for Flutter 3.44+ apps. We -// therefore apply kotlin-android (and pull its classpath) whenever AGP -// won't provide Kotlin itself: AGP < 9, or AGP 9+ with built-in Kotlin -// opted out. See: +// Apply the legacy kotlin-android plugin only when AGP isn't providing +// Kotlin itself — i.e. AGP < 9, or AGP 9+ with `android.builtInKotlin=false` +// (the Flutter 3.44+ migrator default). See: // https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors buildscript { ext.kotlin_version = '1.9.20' @@ -69,9 +65,8 @@ android { } } -// When we apply kotlin-android ourselves, configure jvmTarget through the -// legacy android.kotlinOptions DSL it exposes. Otherwise AGP 9+'s built-in -// Kotlin is in play and we configure it via the new top-level kotlin DSL. +// jvmTarget DSL matches the Kotlin plugin in use: `android.kotlinOptions` +// for kotlin-android, top-level `kotlin {}` for AGP's built-in Kotlin. if (applyLegacyKgp) { android { kotlinOptions { From 2a008305d4f1d0fa3476f08f406a80aa836d073b Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 1 Jun 2026 13:13:05 +0200 Subject: [PATCH 13/13] Drop time-bound Flutter migrator detail from comment Co-authored-by: Cursor --- purchases_ui_flutter/android/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index 7441930fc..f36ba3bc0 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -2,8 +2,7 @@ group 'com.revenuecat.purchases_ui_flutter' version '10.2.0' // Apply the legacy kotlin-android plugin only when AGP isn't providing -// Kotlin itself — i.e. AGP < 9, or AGP 9+ with `android.builtInKotlin=false` -// (the Flutter 3.44+ migrator default). See: +// Kotlin itself — i.e. AGP < 9, or AGP 9+ with `android.builtInKotlin=false`. See: // https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors buildscript { ext.kotlin_version = '1.9.20'