build: update to AGP 9 (AGP 9.2.1, Gradle 9.4.1, Kotlin 2.2.21) - #3964
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
2 builds increased size
TestPurchasesUIAndroidCompatibility 1.0 (1)
|
| Item | Install Size Change | Download Size Change |
|---|---|---|
| kotlin.jvm.internal.ClassReference$Companion | ⬆️ 5.5 kB | ⬆️ 2.0 kB |
| 📝 com.android.tools.r8.DesugarVarHandle | ⬆️ 7.0 kB | ⬆️ 1.7 kB |
| 📝 kotlin.ContextParametersKt__ContextKt | ⬆️ 3.7 kB | ⬆️ 1.3 kB |
| com.revenuecat.purchases.PurchasesOrchestrator | ⬇️ -4.8 kB | ⬇️ -1.1 kB |
| kotlin.collections.ArraysKt___ArraysKt | ⬆️ 3.5 kB | ⬆️ 921 B |
SDKSizeTesting 1.0 (1)
com.revenuecat.testapps.sdksizetesting
⚖️ Compare build
⏱️ Analyze build performance
Total install size change: ⬆️ 152.1 kB (0.42%)
Total download size change: ⬆️ 34.4 kB (0.28%)
Largest size changes
| Item | Install Size Change | Download Size Change |
|---|---|---|
| kotlin.jvm.internal.ClassReference$Companion | ⬆️ 4.9 kB | ⬆️ 1.6 kB |
| baseline.prof | ⬆️ 1.5 kB | ⬆️ 1.5 kB |
| 📝 kotlin.ContextParametersKt__ContextKt | ⬆️ 2.5 kB | ⬆️ 808 B |
| Other | ⬆️ 143.2 kB | ⬆️ 30.5 kB |
🛸 Powered by Emerge Tools
📸 Snapshot Test31 modified, 590 unchanged
🛸 Powered by Emerge Tools |
0bc28e8 to
a9115ff
Compare
a9115ff to
3ed8fa2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 11.0-dev #3964 +/- ##
============================================
+ Coverage 83.19% 83.22% +0.03%
============================================
Files 448 448
Lines 18098 18095 -3
Branches 2736 2736
============================================
+ Hits 15057 15060 +3
+ Misses 2147 2141 -6
Partials 894 894 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
3ed8fa2 to
f8407d6
Compare
38b3a97 to
7e959d5
Compare
7e959d5 to
a22eb30
Compare
… alpha05
These cannot be separated. Kotlin 2.3 needs Poko 0.21+, which requires
Gradle 9.0, which is what AGP 9 runs on; and Paparazzi alpha02 breaks on
Gradle 9 while alpha05 ships Kotlin 2.3 metadata, so it cannot land
earlier either.
Forced along the way:
- kotlinLanguage 1.8 to 2.0. Kotlin 2.3 rejects language version 1.8
outright, and anything below 2.0 selects the K1 frontend, so this is
also the switch to K2.
- kotlinOptions{} is AGP DSL that AGP 9 removes, and the String jvmTarget
form is gone in Kotlin 2.3, so three app modules move to the Kotlin
compilerOptions DSL.
- sdksizetesting is a standalone build; its Kotlin only has to read the
SDK's metadata, and a compiler reads one minor ahead, so 2.2.21 suffices.
- One opt-in the K1 frontend did not require at the call site.
AGP 9 enables built-in Kotlin and rejects the kotlin-android plugin.
Opting out keeps this change reviewable; migrating to built-in Kotlin is
the follow-up, and both flags are removed by AGP 10.
AGP 9 removed the task, whose only job here was to populate ~/.gradle between the orb's restore and save. Deleting just the task would leave restore immediately followed by save, and since the orb keys that cache on the root build's files and save_cache is write-once, a job could claim the key with an unpopulated cache. Only the admob lane builds in the root build, so it keeps restore and save. The other two lanes build nested Gradle projects and now restore only.
The three nested example builds resolve purchases 10.17.0-SNAPSHOT from the Sonatype snapshot repo and were pinned at Kotlin 1.8.22 / 1.9.0. A compiler reads metadata at most one minor ahead, so once main publishes a snapshot built with 2.3.x they stop compiling. Nothing here would have caught it: their jobs only run in snapshot-deploy-sample-app-tests, on main, after the snapshot is published. Kotlin 2.0+ also requires the Compose Compiler Gradle plugin wherever compose is enabled.
AGP 9 only creates unit tests for the testBuildType variant, so release variants silently lose theirs. Release is what we publish, and the only variant where BuildConfig.DEBUG is false, which gates debug logging, extra request logging, WebView contents debugging and the release-only source sets in ui/debugview and ui/revenuecatui. HasUnitTestBuilder is still available in AGP 9.3.1, so the library convention re-enables them for every module. Also update the documented minimum Kotlin: the published kotlin-stdlib is now 2.3.21, whose metadata a compiler reads only one minor ahead, so consumers need 2.2.0.
AGP 9 requires KGP 2.2.10 or higher; 2.3.21 was our choice, not a requirement, and it costs more than it saves. Kotlin 2.3.21 rejects languageVersion 1.8 outright, which forced the K1 to K2 frontend migration into this bump. It also publishes stdlib metadata 2.3.0, which no released React Native can read: RN pins KGP 2.0.21 through 0.79 and 2.1.20 through 0.86, and only RN main pins 2.2.0. Every React Native app would have needed an explicit KGP override. At 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched, and languageVersion stays at 1.8 so the K2 migration moves out of this release. Poko follows to 0.20.2, the release compiled against 2.2.21; it runs on Gradle 9.7 despite 0.21.0 being what raised Poko's own minimum Gradle to 9.0. The opt-in on SubscriberAttributesManager.setAttributes was only needed by the K2 frontend and is removed again. Consumer floor verified in both directions against the release AAR plus the stdlib we publish: a consumer on Kotlin 2.1.21 compiles, and 2.0.21 and 1.9.24 both fail with "metadata is 2.2.0". README and the new v11 migration guide say 2.1.0.
:codegen is a java-gradle-plugin module, so it compiles against the Kotlin that Gradle embeds, and a compiler reads metadata at most one minor ahead. Gradle 9.7.0 embeds Kotlin 2.4.0, which our 2.2.21 compiler cannot read; CI failed in prepare-tests with "metadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0". Embedded Kotlin by Gradle release: 9.3.1 ships 2.2.21, 9.4.1 ships 2.3.0, 9.5.1 ships 2.3.20, 9.7.0 ships 2.4.0. So Kotlin 2.2.x needs Gradle 9.4.1 or lower. AGP 9.3.1 requires Gradle 9.5.0 or higher, so it cannot be used with a Gradle we can read. AGP 9.2.1 is the newest release that runs on Gradle 9.4.1, verified along with 9.1.1 and 9.0.1. Published AAR contract is unchanged: minCompileSdk=1, minAndroidGradlePluginVersion=1.0.0, and no api*.txt diff.
8f307c7 to
1395e6b
Compare
Drop the "must be able to read the SDK's Kotlin metadata" comments from the four sample catalogs, and both CircleCI cache comments. They justify a version choice or an absent step, which belongs in a commit message rather than in the tree. The metadata ones were also stale: they still claimed the SDK publishes 2.3.x. `baselineprofile` 1.5.0-beta01 to 1.5.0-rc02. 1.4.1 still builds on AGP 9.2.1, but warns "tested with versions below Android Gradle Plugin version 9.0.0-alpha01" on all seven projects that apply the plugin; 1.5.0 raises that ceiling. rc02 rather than beta01 since it is published. `enableUnitTest` uses a hard cast. `LibraryVariantBuilder` already extends `HasUnitTestBuilder` in 9.2.1, so `as?` could only ever hide a future AGP dropping it, which is the exact regression the line exists to prevent. Remove `composeOptions.kotlinCompilerExtensionVersion` from the two samples that gained the Compose plugin; it is ignored once that plugin is applied. Reword the `android.newDsl` comment. It is `kotlin-android` that needs the old DSL, since it casts the android extension to `BaseExtension`, not the other way round.
…e comments
The three `kotlin { compilerOptions { jvmTarget } }` blocks that replaced `kotlinOptions{}`
in `checkpointtester`, `rcttester` and `e2etests` were appended after `dependencies {}`,
leaving them 25 lines away from the `compileOptions` they have to agree with. Every other
build script in the repo puts Kotlin compiler config between `android {}` and
`dependencies {}`. Pure move: the compile tasks stay up to date across it.
Drop `# Can't update until we use more recent kotlin. 1.6.0 uses Kotlin 1.9.0` above
`kotlinxSerializationJSON`. Kotlin is 2.2.21 now, so the stated blocker is gone. Not bumping
that dependency here.
Trim the duplicate sentence from the v11 guide intro; the 2.1.0 minimum was stated three
times in 24 lines.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 563bcdd. Configure here.
The lane requires prepare-tests, whose only job is warming the shared Gradle cache, and save_cache is first-write-wins per key. So this save could never claim the key: it uploaded nothing and logged that the cache already existed. Leaves all three sample lanes restore-only and symmetric, rather than one of them writing because it happens to build in the root project.
> [!NOTE] > This targets branch `11.0-dev` which will become the epic branch for the upcoming major release. Once this first PR is merged, I'll open a continuous one from that one to `main` until we are ready to release it altogether. - Moves the SDK's build toolchain to AGP 9: | Dependency | From | To | |---|---|---| | AGP | 8.13.2 | **9.2.1** | | Gradle | 8.14.5 | **9.4.1** | | Kotlin | 2.0.21 | **2.2.21** | | Poko | 0.17.2 | **0.20.2** | | Paparazzi | 2.0.0-alpha02 | **2.0.0-alpha05** | - No public API change and no `api*.txt` diff. - **Why this becomes a major**: this transitively raises the miminum kotlin version for consumers to 2.1.0. up from 1.8.0. - Kotlin lands on **2.2.21, the minimum required by AGP 9**, - AGP is capped at **9.2.1** rather than latest: AGP 9.3.x requires Gradle 9.5+, and Gradle 9.5+ embeds a Kotlin that a 2.2 compiler cannot read, bumping consumers to an even higher minimum Kotlin version. - Nothing downstream needs AGP 9 or Gradle 9, just the Kotlin bump. The published AAR metadata is unchanged (`minCompileSdk=1`, `minAndroidGradlePluginVersion=1.0.0`). Companion docs PR: RevenueCat/docs#1942 Companion PHC PR: RevenueCat/purchases-hybrid-common#1844 ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 8 is increasingly behind, and AGP 9 removes a number of DSL surfaces we still used. The prerequisite work already landed separately in #3931, #3961 and #3963. AGP 9 has a runtime dependency on Kotlin Gradle Plugin **2.2.10 or higher**, so a Kotlin bump is not optional here. Everything above that floor is a choice, and this PR deliberately takes the minimum. ### Why not Kotlin 2.3.x An earlier revision of this branch used Kotlin 2.3.21. Measuring the cost changed the decision: - **It breaks every React Native consumer.** We publish `kotlin-stdlib` at `compile` scope and Gradle resolves consumers onto it. A compiler reads metadata at most one minor ahead. Kotlin 2.3.x publishes metadata 2.3.0, and React Native's own Gradle plugin pins the compiler: 2.0.21 through RN 0.79, 2.1.20 through RN 0.86, with only RN `main` on 2.2.0. Reproduced on a real RN 0.86 app: `metadata version is 2.3.0 / compiler version 2.1.0 can read versions up to 2.2.0`. Every RN app would have needed an explicit `kotlin-gradle-plugin` version in `android/build.gradle`. - **It forces the K2 migration.** Kotlin 2.3.21 rejects `languageVersion 1.8` outright. 2.2.x still accepts it, so the frontend migration stays out of a version bump where it does not belong. - **It does not avoid a second bump anyway.** Kotlin 2.4 removes `languageVersion 1.8` regardless. At Kotlin 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched. ### Why AGP 9.2.1 and Gradle 9.4.1 This is the non-obvious constraint, and CI found it rather than local builds. `:codegen` is a `java-gradle-plugin` module, so it compiles against the Kotlin that **Gradle itself embeds**. Embedded Kotlin by Gradle release: | Gradle | Embedded Kotlin | Readable by a 2.2 compiler (max 2.3.0) | |---|---|---| | 9.3.1 | 2.2.21 | yes | | 9.4.1 | 2.3.0 | yes, exactly at the limit | | 9.5.1 | 2.3.20 | no | | 9.7.0 | 2.4.0 | no | Gradle 9.7.0 therefore failed `prepare-tests` with `metadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0`. AGP 9.3.x requires Gradle 9.5.0 or higher (`Minimum supported Gradle version is 9.5.0`), which is exactly the range we cannot read. **AGP 9.2.1 is the newest release that runs on Gradle 9.4.1**; 9.1.1 and 9.0.1 were also confirmed to work, 9.3.1 was confirmed not to. So the AGP ceiling here is a consequence of the Kotlin floor, not an independent preference. When the deferred Kotlin 2.3 bump happens, AGP and Gradle can move up with it. ### Also in this PR - Standalone sample apps (`CustomEntitlementComputationSample`, `MagicWeather`, `MagicWeatherCompose`) move to Kotlin 2.2.21 and gain the `compose-compiler` plugin alias. They consume our published artifacts, so they need a compiler that can read our metadata. - Unit tests are re-enabled on the release variant via `HasUnitTestBuilder.enableUnitTest`, because AGP 9 only creates unit tests for the `testBuildType` variant and the published variant is release. - The `androidDependencies` cache-warm CI step is dropped; AGP 9 removed that task. - `migrations/v11-MIGRATION.md` documents the new Kotlin floor, mirroring the v9 guide. - README `Requirements` now says Kotlin 2.1.0+. ### Deferred on purpose - **Kotlin 2.3 and the K1 to K2 migration**, until React Native ships the KGP 2.2 pin already on its `main` branch. Doing it sooner buys nothing and costs every RN app a migration step. - **AGP 9 built-in Kotlin** (previously #3965). Not breaking and not required; the `android.builtInKotlin=false` / `android.newDsl=false` opt-out only has to go at AGP 10. ### Testing - `assembleDebug` across all modules; release AARs for `:purchases` and `:ui:revenuecatui`. - `:purchases` unit tests, `:ui:debugview:verifyPaparazziDefaultsDebug`, `detektAll`. - `scripts/api-check.sh` with **zero** `api*.txt` diff. - Published contract checked directly: POM carries `kotlin-stdlib:2.2.21` at `compile` scope, the release AAR still reports `minCompileSdk=1` and `minAndroidGradlePluginVersion=1.0.0`, and our own classes carry metadata `mv = {1, 8, 0}`. - Consumer floor measured against the real release AAR plus the stdlib we publish: Kotlin 2.1.21 compiles; 2.0.21 and 1.9.24 fail. That is what the 2.1.0 claim in the README rests on. ### Downstream The Kotlin floor is what propagates, not AGP. Nothing downstream needs AGP 9 or Gradle 9. The only direct consumer is purchases-hybrid-common, which re-exposes us at compile scope via `api(libs.purchases)`. RevenueCat/purchases-hybrid-common#1844 raises its Kotlin accordingly, and the hybrid SDKs pick the floor up from there rather than from this repo. Documentation follows separately: - RevenueCat/docs#1942 adds the Kotlin minimum to the Android, Flutter and React Native installation pages, and adds the 9.x-to-10.x and 10.x-to-11.x migration guides the site was missing. - RevenueCat/purchases-flutter#1876 states the Kotlin minimum in that repo's README. Flutter applies the version from the app's `android/settings.gradle`, so the requirement lands on the consuming app and the Flutter version itself does not move. - RevenueCat/react-native-purchases#1925 raises the React Native floor to 0.80.0, the first release whose Gradle plugin pins a Kotlin that can read our metadata. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Major toolchain and minimum Kotlin version changes affect all consumers and CI, though public API and published AAR AGP/minSdk metadata are intentionally unchanged. > > **Overview** > This PR moves the SDK build to **AGP 9.2.1**, **Gradle 9.4.1**, and **Kotlin 2.2.21**, and bumps related tooling (Poko, Paparazzi, baseline profile plugin). **`gradle.properties`** opts out of AGP 9’s built-in Kotlin and new DSL for now. > > The **consumer-facing change** is a higher Kotlin floor: README and **`migrations/v11-MIGRATION.md`** document **Kotlin 2.1.0+** (up from 1.8.0), driven by published stdlib metadata—not a public API change. > > **Build-logic** re-enables unit tests on library variants under AGP 9 via **`enableUnitTest`**, and keeps **`aarMetadata.minCompileSdk = 1`** so published AAR requirements don’t tighten. > > Compose sample apps switch to the **Kotlin Compose compiler plugin** and drop **`composeOptions.kotlinCompilerExtensionVersion`**. Several modules replace deprecated **`kotlinOptions`** with **`kotlin.compilerOptions.jvmTarget`**. > > **CircleCI** drops the removed **`androidDependencies`** warmup and uses Gradle cache restore on standalone sample builds instead. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b6b4df. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Removes the `android.builtInKotlin=false` and `android.newDsl=false` opt-out that #3964 added, from the root build and from both standalone sample builds, and takes `kotlin-android` out of 11 build scripts and the 3 convention plugins. - `ui/debugview/api.txt` loses `InternalDebugRevenueCatScreenViewModelFactory`. Metalava now analyses the release variant, and that class lives in `src/debug/kotlin`. The release AAR contains zero occurrences of it, so it never shipped and the entry was wrong - Fixes `:dokkaGenerate`, which is already broken on `11.0-dev`. Built-in Kotlin registers Dokka source sets per Android variant instead of per Kotlin source set, and registers them lazily, so both `:purchases` and `:ui:revenuecatui` now select the documented variant through `configureEach`. - Bumps `androidx.navigation` 2.5.3 to 2.9.8. safeargs 2.5.3 detects AGP through the old DSL and hard fails, so the navigation bump is a prerequisite for the new DSL rather than an independent upgrade. ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 9 enables built-in Kotlin and rejects the `org.jetbrains.kotlin.android` plugin outright: ``` The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0. ``` Opting out needs two flags together, `android.builtInKotlin=false` and `android.newDsl=false`, because the kotlin-android plugin casts the android extension to `BaseExtension`, which the new DSL drops. #3964 took that opt-out so the version bump stayed reviewable. AGP 10 removes the opt-out, so the migration has to happen regardless; doing it separately keeps the two diffs legible. ### Description Four things blocked the migration, each hidden behind the previous. **The old DSL extension types are gone.** `com.android.build.gradle.BaseExtension` and `com.android.build.gradle.LibraryExtension` do not exist once the new DSL is active. The convention plugins now configure `com.android.build.api.dsl.ApplicationExtension` and `com.android.build.api.dsl.LibraryExtension`, and set `compileSdk` directly rather than calling `compileSdkVersion()`. `KotlinAndroidProjectExtension` stays as is: AGP's built-in Kotlin still registers it. **safeargs 2.5.3 cannot see AGP 9.** It fails with `safeargs plugin must be used with android plugin`, because its detection goes through the old DSL. The 2.5.3 jar references `com/android/build/gradle/BaseExtension`, `AppExtension` and `gradle/api/BaseVariant`; the 2.9.8 jar references only `com.android.build.api.variant.*`. `examples/purchase-tester` is the only module applying the plugin, and it gets its own commit. **Dokka source sets changed shape.** With the kotlin-android plugin, Dokka saw the Kotlin source sets and the config addressed `main`, `defaults` and `customEntitlementComputation`. Built-in Kotlin instead registers one source set per Android variant, and registers them after the `dokka { }` block runs, so `named("defaultsRelease")` throws even though the name is valid later. Both modules now select through `matching { }.configureEach`. `:purchases` keeps `reportUndocumented`, `skipDeprecated`, the Android external documentation link, both source links and the `paywalls.components` suppression, all moved onto the selected variant. The explicit `customEntitlementComputation` suppression is load-bearing, not redundant: Dokka defaults `suppress` to `!isPublishable` and `customEntitlementComputationRelease` is published, so it would otherwise be documented. **`kotlin-test` stops carrying JUnit.** `:feature:galaxy` names `kotlin-test-junit` instead. Verified rather than inherited: with plain `kotlin-test`, `org.junit.Test` is unresolved across all 25 test methods. **Not visible in the diff:** `./gradlew :dokkaGenerate` fails on the base commit with `Dokka cannot generate documentation for Android projects with multiple enabled variants that have common source roots`. The `docs-deploy` job that runs it is gated on release tags, so #3964 never exercised it. This PR is what makes it pass again, which is a bigger deal than the diff makes it look. **Worth noting separately:** AGP 9.2.1 declares `kotlin-gradle-plugin:2.2.10`, so built-in Kotlin takes its compiler from buildscript classpath resolution rather than from our catalog. Confirmed the published contract from #3964 is intact rather than assumed: | check | result | | --- | --- | | resolved KGP | `2.2.21`, not AGP's bundled `2.2.10` | | `:purchases` POM | `kotlin-stdlib:2.2.21` at `compile` scope | | release AAR metadata | `mv=[1,8,0]`, so `languageVersion` 1.8 survives | ### Regression gates - `./gradlew :dokkaGenerate` for the Dokka rework. Generates 1367 pages with `com.revenuecat.purchases.paywalls.components` still suppressed. - `:feature:galaxy:testDefaultsDebugUnitTest` for the `kotlin-test-junit` swap. Needs `--rerun-tasks`: a cached run passes even with the dependency reverted, which is how this could slip through. - `:examples:purchase-tester:assembleDebug` for the navigation bump. **Rejected:** - Wiring the Dokka variant selector to `ANDROID_VARIANT_TO_PUBLISH` instead of hardcoding `"defaultsRelease"` twice. `fastlane/Fastfile:295` rewrites that property per publish, so a `customEntitlementComputation` publish would point Dokka at exactly the variant `:purchases` suppresses. - Hoisting the shared Dokka config into `ConfigureDokka.kt`. Dokka's types are not on build-logic's compile classpath, so it needs the reflection pattern from `ConfigureMetalava.kt`, and sharing `reportUndocumented.set(true)` would newly turn it on for the four public-library modules that have no `dokka { }` block. - Moving the five hand-rolled app modules onto `revenuecat-android-application`, which would collapse 5 of the 6 per-module edits. Each keeps 2 to 3 overrides, and both `testpurchases*` modules deliberately pin old `compileSdk`/`targetSdk` to test compatibility. Worth a separate PR, not this one. **Limitations:** - `examples/MagicWeather` and `test-apps/sdksizetesting` still apply `kotlin-android`. They are separate builds pinned to AGP 8.13.2, so they keep the plugin correctly and are untouched. - `purchases/build.gradle.kts:120` has a dead `androidComponents { onVariants }` block guarding a `"free"` product flavor that does not exist in the repo. Pre-existing and unrelated, left alone. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Wide Gradle/AGP convention changes affect every Android module’s compile and doc generation; navigation and Dokka behavior are prerequisites for CI release docs, with limited runtime SDK impact. > > **Overview** > Completes the AGP 9 migration by removing the **`android.builtInKotlin=false`** / **`android.newDsl=false`** opt-outs and dropping **`org.jetbrains.kotlin.android`** across convention plugins, app modules, samples, and the version catalog. > > Build-logic now configures **`ApplicationExtension`** / **`LibraryExtension`** from the new DSL (e.g. **`compileSdk`** instead of **`BaseExtension`**). **`:purchases`** and **`:ui:revenuecatui`** Dokka blocks use **`matching { }.configureEach`** so documentation targets **`defaultsRelease`** and suppresses **`customEntitlementComputation`** variants after AGP registers per-variant source sets lazily. > > **`androidx.navigation`** is bumped **2.5.3 → 2.9.8** so Safe Args works with the new DSL. **`:feature:galaxy`** switches unit tests to **`kotlin-test-junit`**. **`ui/debugview/api.txt`** drops **`InternalDebugRevenueCatScreenViewModelFactory`** (debug-only; not in the release API Metalava analyzes). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e45c589. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
> [!NOTE] > This targets branch `11.0-dev` which will become the epic branch for the upcoming major release. Once this first PR is merged, I'll open a continuous one from that one to `main` until we are ready to release it altogether. - Moves the SDK's build toolchain to AGP 9: | Dependency | From | To | |---|---|---| | AGP | 8.13.2 | **9.2.1** | | Gradle | 8.14.5 | **9.4.1** | | Kotlin | 2.0.21 | **2.2.21** | | Poko | 0.17.2 | **0.20.2** | | Paparazzi | 2.0.0-alpha02 | **2.0.0-alpha05** | - No public API change and no `api*.txt` diff. - **Why this becomes a major**: this transitively raises the miminum kotlin version for consumers to 2.1.0. up from 1.8.0. - Kotlin lands on **2.2.21, the minimum required by AGP 9**, - AGP is capped at **9.2.1** rather than latest: AGP 9.3.x requires Gradle 9.5+, and Gradle 9.5+ embeds a Kotlin that a 2.2 compiler cannot read, bumping consumers to an even higher minimum Kotlin version. - Nothing downstream needs AGP 9 or Gradle 9, just the Kotlin bump. The published AAR metadata is unchanged (`minCompileSdk=1`, `minAndroidGradlePluginVersion=1.0.0`). Companion docs PR: RevenueCat/docs#1942 Companion PHC PR: RevenueCat/purchases-hybrid-common#1844 ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 8 is increasingly behind, and AGP 9 removes a number of DSL surfaces we still used. The prerequisite work already landed separately in #3931, #3961 and #3963. AGP 9 has a runtime dependency on Kotlin Gradle Plugin **2.2.10 or higher**, so a Kotlin bump is not optional here. Everything above that floor is a choice, and this PR deliberately takes the minimum. ### Why not Kotlin 2.3.x An earlier revision of this branch used Kotlin 2.3.21. Measuring the cost changed the decision: - **It breaks every React Native consumer.** We publish `kotlin-stdlib` at `compile` scope and Gradle resolves consumers onto it. A compiler reads metadata at most one minor ahead. Kotlin 2.3.x publishes metadata 2.3.0, and React Native's own Gradle plugin pins the compiler: 2.0.21 through RN 0.79, 2.1.20 through RN 0.86, with only RN `main` on 2.2.0. Reproduced on a real RN 0.86 app: `metadata version is 2.3.0 / compiler version 2.1.0 can read versions up to 2.2.0`. Every RN app would have needed an explicit `kotlin-gradle-plugin` version in `android/build.gradle`. - **It forces the K2 migration.** Kotlin 2.3.21 rejects `languageVersion 1.8` outright. 2.2.x still accepts it, so the frontend migration stays out of a version bump where it does not belong. - **It does not avoid a second bump anyway.** Kotlin 2.4 removes `languageVersion 1.8` regardless. At Kotlin 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched. ### Why AGP 9.2.1 and Gradle 9.4.1 This is the non-obvious constraint, and CI found it rather than local builds. `:codegen` is a `java-gradle-plugin` module, so it compiles against the Kotlin that **Gradle itself embeds**. Embedded Kotlin by Gradle release: | Gradle | Embedded Kotlin | Readable by a 2.2 compiler (max 2.3.0) | |---|---|---| | 9.3.1 | 2.2.21 | yes | | 9.4.1 | 2.3.0 | yes, exactly at the limit | | 9.5.1 | 2.3.20 | no | | 9.7.0 | 2.4.0 | no | Gradle 9.7.0 therefore failed `prepare-tests` with `metadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0`. AGP 9.3.x requires Gradle 9.5.0 or higher (`Minimum supported Gradle version is 9.5.0`), which is exactly the range we cannot read. **AGP 9.2.1 is the newest release that runs on Gradle 9.4.1**; 9.1.1 and 9.0.1 were also confirmed to work, 9.3.1 was confirmed not to. So the AGP ceiling here is a consequence of the Kotlin floor, not an independent preference. When the deferred Kotlin 2.3 bump happens, AGP and Gradle can move up with it. ### Also in this PR - Standalone sample apps (`CustomEntitlementComputationSample`, `MagicWeather`, `MagicWeatherCompose`) move to Kotlin 2.2.21 and gain the `compose-compiler` plugin alias. They consume our published artifacts, so they need a compiler that can read our metadata. - Unit tests are re-enabled on the release variant via `HasUnitTestBuilder.enableUnitTest`, because AGP 9 only creates unit tests for the `testBuildType` variant and the published variant is release. - The `androidDependencies` cache-warm CI step is dropped; AGP 9 removed that task. - `migrations/v11-MIGRATION.md` documents the new Kotlin floor, mirroring the v9 guide. - README `Requirements` now says Kotlin 2.1.0+. ### Deferred on purpose - **Kotlin 2.3 and the K1 to K2 migration**, until React Native ships the KGP 2.2 pin already on its `main` branch. Doing it sooner buys nothing and costs every RN app a migration step. - **AGP 9 built-in Kotlin** (previously #3965). Not breaking and not required; the `android.builtInKotlin=false` / `android.newDsl=false` opt-out only has to go at AGP 10. ### Testing - `assembleDebug` across all modules; release AARs for `:purchases` and `:ui:revenuecatui`. - `:purchases` unit tests, `:ui:debugview:verifyPaparazziDefaultsDebug`, `detektAll`. - `scripts/api-check.sh` with **zero** `api*.txt` diff. - Published contract checked directly: POM carries `kotlin-stdlib:2.2.21` at `compile` scope, the release AAR still reports `minCompileSdk=1` and `minAndroidGradlePluginVersion=1.0.0`, and our own classes carry metadata `mv = {1, 8, 0}`. - Consumer floor measured against the real release AAR plus the stdlib we publish: Kotlin 2.1.21 compiles; 2.0.21 and 1.9.24 fail. That is what the 2.1.0 claim in the README rests on. ### Downstream The Kotlin floor is what propagates, not AGP. Nothing downstream needs AGP 9 or Gradle 9. The only direct consumer is purchases-hybrid-common, which re-exposes us at compile scope via `api(libs.purchases)`. RevenueCat/purchases-hybrid-common#1844 raises its Kotlin accordingly, and the hybrid SDKs pick the floor up from there rather than from this repo. Documentation follows separately: - RevenueCat/docs#1942 adds the Kotlin minimum to the Android, Flutter and React Native installation pages, and adds the 9.x-to-10.x and 10.x-to-11.x migration guides the site was missing. - RevenueCat/purchases-flutter#1876 states the Kotlin minimum in that repo's README. Flutter applies the version from the app's `android/settings.gradle`, so the requirement lands on the consuming app and the Flutter version itself does not move. - RevenueCat/react-native-purchases#1925 raises the React Native floor to 0.80.0, the first release whose Gradle plugin pins a Kotlin that can read our metadata. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Major toolchain and minimum Kotlin version changes affect all consumers and CI, though public API and published AAR AGP/minSdk metadata are intentionally unchanged. > > **Overview** > This PR moves the SDK build to **AGP 9.2.1**, **Gradle 9.4.1**, and **Kotlin 2.2.21**, and bumps related tooling (Poko, Paparazzi, baseline profile plugin). **`gradle.properties`** opts out of AGP 9’s built-in Kotlin and new DSL for now. > > The **consumer-facing change** is a higher Kotlin floor: README and **`migrations/v11-MIGRATION.md`** document **Kotlin 2.1.0+** (up from 1.8.0), driven by published stdlib metadata—not a public API change. > > **Build-logic** re-enables unit tests on library variants under AGP 9 via **`enableUnitTest`**, and keeps **`aarMetadata.minCompileSdk = 1`** so published AAR requirements don’t tighten. > > Compose sample apps switch to the **Kotlin Compose compiler plugin** and drop **`composeOptions.kotlinCompilerExtensionVersion`**. Several modules replace deprecated **`kotlinOptions`** with **`kotlin.compilerOptions.jvmTarget`**. > > **CircleCI** drops the removed **`androidDependencies`** warmup and uses Gradle cache restore on standalone sample builds instead. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b6b4df. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Removes the `android.builtInKotlin=false` and `android.newDsl=false` opt-out that #3964 added, from the root build and from both standalone sample builds, and takes `kotlin-android` out of 11 build scripts and the 3 convention plugins. - `ui/debugview/api.txt` loses `InternalDebugRevenueCatScreenViewModelFactory`. Metalava now analyses the release variant, and that class lives in `src/debug/kotlin`. The release AAR contains zero occurrences of it, so it never shipped and the entry was wrong - Fixes `:dokkaGenerate`, which is already broken on `11.0-dev`. Built-in Kotlin registers Dokka source sets per Android variant instead of per Kotlin source set, and registers them lazily, so both `:purchases` and `:ui:revenuecatui` now select the documented variant through `configureEach`. - Bumps `androidx.navigation` 2.5.3 to 2.9.8. safeargs 2.5.3 detects AGP through the old DSL and hard fails, so the navigation bump is a prerequisite for the new DSL rather than an independent upgrade. ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 9 enables built-in Kotlin and rejects the `org.jetbrains.kotlin.android` plugin outright: ``` The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0. ``` Opting out needs two flags together, `android.builtInKotlin=false` and `android.newDsl=false`, because the kotlin-android plugin casts the android extension to `BaseExtension`, which the new DSL drops. #3964 took that opt-out so the version bump stayed reviewable. AGP 10 removes the opt-out, so the migration has to happen regardless; doing it separately keeps the two diffs legible. ### Description Four things blocked the migration, each hidden behind the previous. **The old DSL extension types are gone.** `com.android.build.gradle.BaseExtension` and `com.android.build.gradle.LibraryExtension` do not exist once the new DSL is active. The convention plugins now configure `com.android.build.api.dsl.ApplicationExtension` and `com.android.build.api.dsl.LibraryExtension`, and set `compileSdk` directly rather than calling `compileSdkVersion()`. `KotlinAndroidProjectExtension` stays as is: AGP's built-in Kotlin still registers it. **safeargs 2.5.3 cannot see AGP 9.** It fails with `safeargs plugin must be used with android plugin`, because its detection goes through the old DSL. The 2.5.3 jar references `com/android/build/gradle/BaseExtension`, `AppExtension` and `gradle/api/BaseVariant`; the 2.9.8 jar references only `com.android.build.api.variant.*`. `examples/purchase-tester` is the only module applying the plugin, and it gets its own commit. **Dokka source sets changed shape.** With the kotlin-android plugin, Dokka saw the Kotlin source sets and the config addressed `main`, `defaults` and `customEntitlementComputation`. Built-in Kotlin instead registers one source set per Android variant, and registers them after the `dokka { }` block runs, so `named("defaultsRelease")` throws even though the name is valid later. Both modules now select through `matching { }.configureEach`. `:purchases` keeps `reportUndocumented`, `skipDeprecated`, the Android external documentation link, both source links and the `paywalls.components` suppression, all moved onto the selected variant. The explicit `customEntitlementComputation` suppression is load-bearing, not redundant: Dokka defaults `suppress` to `!isPublishable` and `customEntitlementComputationRelease` is published, so it would otherwise be documented. **`kotlin-test` stops carrying JUnit.** `:feature:galaxy` names `kotlin-test-junit` instead. Verified rather than inherited: with plain `kotlin-test`, `org.junit.Test` is unresolved across all 25 test methods. **Not visible in the diff:** `./gradlew :dokkaGenerate` fails on the base commit with `Dokka cannot generate documentation for Android projects with multiple enabled variants that have common source roots`. The `docs-deploy` job that runs it is gated on release tags, so #3964 never exercised it. This PR is what makes it pass again, which is a bigger deal than the diff makes it look. **Worth noting separately:** AGP 9.2.1 declares `kotlin-gradle-plugin:2.2.10`, so built-in Kotlin takes its compiler from buildscript classpath resolution rather than from our catalog. Confirmed the published contract from #3964 is intact rather than assumed: | check | result | | --- | --- | | resolved KGP | `2.2.21`, not AGP's bundled `2.2.10` | | `:purchases` POM | `kotlin-stdlib:2.2.21` at `compile` scope | | release AAR metadata | `mv=[1,8,0]`, so `languageVersion` 1.8 survives | ### Regression gates - `./gradlew :dokkaGenerate` for the Dokka rework. Generates 1367 pages with `com.revenuecat.purchases.paywalls.components` still suppressed. - `:feature:galaxy:testDefaultsDebugUnitTest` for the `kotlin-test-junit` swap. Needs `--rerun-tasks`: a cached run passes even with the dependency reverted, which is how this could slip through. - `:examples:purchase-tester:assembleDebug` for the navigation bump. **Rejected:** - Wiring the Dokka variant selector to `ANDROID_VARIANT_TO_PUBLISH` instead of hardcoding `"defaultsRelease"` twice. `fastlane/Fastfile:295` rewrites that property per publish, so a `customEntitlementComputation` publish would point Dokka at exactly the variant `:purchases` suppresses. - Hoisting the shared Dokka config into `ConfigureDokka.kt`. Dokka's types are not on build-logic's compile classpath, so it needs the reflection pattern from `ConfigureMetalava.kt`, and sharing `reportUndocumented.set(true)` would newly turn it on for the four public-library modules that have no `dokka { }` block. - Moving the five hand-rolled app modules onto `revenuecat-android-application`, which would collapse 5 of the 6 per-module edits. Each keeps 2 to 3 overrides, and both `testpurchases*` modules deliberately pin old `compileSdk`/`targetSdk` to test compatibility. Worth a separate PR, not this one. **Limitations:** - `examples/MagicWeather` and `test-apps/sdksizetesting` still apply `kotlin-android`. They are separate builds pinned to AGP 8.13.2, so they keep the plugin correctly and are untouched. - `purchases/build.gradle.kts:120` has a dead `androidComponents { onVariants }` block guarding a `"free"` product flavor that does not exist in the repo. Pre-existing and unrelated, left alone. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Wide Gradle/AGP convention changes affect every Android module’s compile and doc generation; navigation and Dokka behavior are prerequisites for CI release docs, with limited runtime SDK impact. > > **Overview** > Completes the AGP 9 migration by removing the **`android.builtInKotlin=false`** / **`android.newDsl=false`** opt-outs and dropping **`org.jetbrains.kotlin.android`** across convention plugins, app modules, samples, and the version catalog. > > Build-logic now configures **`ApplicationExtension`** / **`LibraryExtension`** from the new DSL (e.g. **`compileSdk`** instead of **`BaseExtension`**). **`:purchases`** and **`:ui:revenuecatui`** Dokka blocks use **`matching { }.configureEach`** so documentation targets **`defaultsRelease`** and suppresses **`customEntitlementComputation`** variants after AGP registers per-variant source sets lazily. > > **`androidx.navigation`** is bumped **2.5.3 → 2.9.8** so Safe Args works with the new DSL. **`:feature:galaxy`** switches unit tests to **`kotlin-test-junit`**. **`ui/debugview/api.txt`** drops **`InternalDebugRevenueCatScreenViewModelFactory`** (debug-only; not in the release API Metalava analyzes). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e45c589. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
> [!NOTE] > This targets branch `11.0-dev` which will become the epic branch for the upcoming major release. Once this first PR is merged, I'll open a continuous one from that one to `main` until we are ready to release it altogether. - Moves the SDK's build toolchain to AGP 9: | Dependency | From | To | |---|---|---| | AGP | 8.13.2 | **9.2.1** | | Gradle | 8.14.5 | **9.4.1** | | Kotlin | 2.0.21 | **2.2.21** | | Poko | 0.17.2 | **0.20.2** | | Paparazzi | 2.0.0-alpha02 | **2.0.0-alpha05** | - No public API change and no `api*.txt` diff. - **Why this becomes a major**: this transitively raises the miminum kotlin version for consumers to 2.1.0. up from 1.8.0. - Kotlin lands on **2.2.21, the minimum required by AGP 9**, - AGP is capped at **9.2.1** rather than latest: AGP 9.3.x requires Gradle 9.5+, and Gradle 9.5+ embeds a Kotlin that a 2.2 compiler cannot read, bumping consumers to an even higher minimum Kotlin version. - Nothing downstream needs AGP 9 or Gradle 9, just the Kotlin bump. The published AAR metadata is unchanged (`minCompileSdk=1`, `minAndroidGradlePluginVersion=1.0.0`). Companion docs PR: RevenueCat/docs#1942 Companion PHC PR: RevenueCat/purchases-hybrid-common#1844 - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> AGP 8 is increasingly behind, and AGP 9 removes a number of DSL surfaces we still used. The prerequisite work already landed separately in #3931, #3961 and #3963. AGP 9 has a runtime dependency on Kotlin Gradle Plugin **2.2.10 or higher**, so a Kotlin bump is not optional here. Everything above that floor is a choice, and this PR deliberately takes the minimum. An earlier revision of this branch used Kotlin 2.3.21. Measuring the cost changed the decision: - **It breaks every React Native consumer.** We publish `kotlin-stdlib` at `compile` scope and Gradle resolves consumers onto it. A compiler reads metadata at most one minor ahead. Kotlin 2.3.x publishes metadata 2.3.0, and React Native's own Gradle plugin pins the compiler: 2.0.21 through RN 0.79, 2.1.20 through RN 0.86, with only RN `main` on 2.2.0. Reproduced on a real RN 0.86 app: `metadata version is 2.3.0 / compiler version 2.1.0 can read versions up to 2.2.0`. Every RN app would have needed an explicit `kotlin-gradle-plugin` version in `android/build.gradle`. - **It forces the K2 migration.** Kotlin 2.3.21 rejects `languageVersion 1.8` outright. 2.2.x still accepts it, so the frontend migration stays out of a version bump where it does not belong. - **It does not avoid a second bump anyway.** Kotlin 2.4 removes `languageVersion 1.8` regardless. At Kotlin 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched. This is the non-obvious constraint, and CI found it rather than local builds. `:codegen` is a `java-gradle-plugin` module, so it compiles against the Kotlin that **Gradle itself embeds**. Embedded Kotlin by Gradle release: | Gradle | Embedded Kotlin | Readable by a 2.2 compiler (max 2.3.0) | |---|---|---| | 9.3.1 | 2.2.21 | yes | | 9.4.1 | 2.3.0 | yes, exactly at the limit | | 9.5.1 | 2.3.20 | no | | 9.7.0 | 2.4.0 | no | Gradle 9.7.0 therefore failed `prepare-tests` with `metadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0`. AGP 9.3.x requires Gradle 9.5.0 or higher (`Minimum supported Gradle version is 9.5.0`), which is exactly the range we cannot read. **AGP 9.2.1 is the newest release that runs on Gradle 9.4.1**; 9.1.1 and 9.0.1 were also confirmed to work, 9.3.1 was confirmed not to. So the AGP ceiling here is a consequence of the Kotlin floor, not an independent preference. When the deferred Kotlin 2.3 bump happens, AGP and Gradle can move up with it. - Standalone sample apps (`CustomEntitlementComputationSample`, `MagicWeather`, `MagicWeatherCompose`) move to Kotlin 2.2.21 and gain the `compose-compiler` plugin alias. They consume our published artifacts, so they need a compiler that can read our metadata. - Unit tests are re-enabled on the release variant via `HasUnitTestBuilder.enableUnitTest`, because AGP 9 only creates unit tests for the `testBuildType` variant and the published variant is release. - The `androidDependencies` cache-warm CI step is dropped; AGP 9 removed that task. - `migrations/v11-MIGRATION.md` documents the new Kotlin floor, mirroring the v9 guide. - README `Requirements` now says Kotlin 2.1.0+. - **Kotlin 2.3 and the K1 to K2 migration**, until React Native ships the KGP 2.2 pin already on its `main` branch. Doing it sooner buys nothing and costs every RN app a migration step. - **AGP 9 built-in Kotlin** (previously #3965). Not breaking and not required; the `android.builtInKotlin=false` / `android.newDsl=false` opt-out only has to go at AGP 10. - `assembleDebug` across all modules; release AARs for `:purchases` and `:ui:revenuecatui`. - `:purchases` unit tests, `:ui:debugview:verifyPaparazziDefaultsDebug`, `detektAll`. - `scripts/api-check.sh` with **zero** `api*.txt` diff. - Published contract checked directly: POM carries `kotlin-stdlib:2.2.21` at `compile` scope, the release AAR still reports `minCompileSdk=1` and `minAndroidGradlePluginVersion=1.0.0`, and our own classes carry metadata `mv = {1, 8, 0}`. - Consumer floor measured against the real release AAR plus the stdlib we publish: Kotlin 2.1.21 compiles; 2.0.21 and 1.9.24 fail. That is what the 2.1.0 claim in the README rests on. The Kotlin floor is what propagates, not AGP. Nothing downstream needs AGP 9 or Gradle 9. The only direct consumer is purchases-hybrid-common, which re-exposes us at compile scope via `api(libs.purchases)`. RevenueCat/purchases-hybrid-common#1844 raises its Kotlin accordingly, and the hybrid SDKs pick the floor up from there rather than from this repo. Documentation follows separately: - RevenueCat/docs#1942 adds the Kotlin minimum to the Android, Flutter and React Native installation pages, and adds the 9.x-to-10.x and 10.x-to-11.x migration guides the site was missing. - RevenueCat/purchases-flutter#1876 states the Kotlin minimum in that repo's README. Flutter applies the version from the app's `android/settings.gradle`, so the requirement lands on the consuming app and the Flutter version itself does not move. - RevenueCat/react-native-purchases#1925 raises the React Native floor to 0.80.0, the first release whose Gradle plugin pins a Kotlin that can read our metadata. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Major toolchain and minimum Kotlin version changes affect all consumers and CI, though public API and published AAR AGP/minSdk metadata are intentionally unchanged. > > **Overview** > This PR moves the SDK build to **AGP 9.2.1**, **Gradle 9.4.1**, and **Kotlin 2.2.21**, and bumps related tooling (Poko, Paparazzi, baseline profile plugin). **`gradle.properties`** opts out of AGP 9’s built-in Kotlin and new DSL for now. > > The **consumer-facing change** is a higher Kotlin floor: README and **`migrations/v11-MIGRATION.md`** document **Kotlin 2.1.0+** (up from 1.8.0), driven by published stdlib metadata—not a public API change. > > **Build-logic** re-enables unit tests on library variants under AGP 9 via **`enableUnitTest`**, and keeps **`aarMetadata.minCompileSdk = 1`** so published AAR requirements don’t tighten. > > Compose sample apps switch to the **Kotlin Compose compiler plugin** and drop **`composeOptions.kotlinCompilerExtensionVersion`**. Several modules replace deprecated **`kotlinOptions`** with **`kotlin.compilerOptions.jvmTarget`**. > > **CircleCI** drops the removed **`androidDependencies`** warmup and uses Gradle cache restore on standalone sample builds instead. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b6b4df. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Removes the `android.builtInKotlin=false` and `android.newDsl=false` opt-out that #3964 added, from the root build and from both standalone sample builds, and takes `kotlin-android` out of 11 build scripts and the 3 convention plugins. - `ui/debugview/api.txt` loses `InternalDebugRevenueCatScreenViewModelFactory`. Metalava now analyses the release variant, and that class lives in `src/debug/kotlin`. The release AAR contains zero occurrences of it, so it never shipped and the entry was wrong - Fixes `:dokkaGenerate`, which is already broken on `11.0-dev`. Built-in Kotlin registers Dokka source sets per Android variant instead of per Kotlin source set, and registers them lazily, so both `:purchases` and `:ui:revenuecatui` now select the documented variant through `configureEach`. - Bumps `androidx.navigation` 2.5.3 to 2.9.8. safeargs 2.5.3 detects AGP through the old DSL and hard fails, so the navigation bump is a prerequisite for the new DSL rather than an independent upgrade. ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 9 enables built-in Kotlin and rejects the `org.jetbrains.kotlin.android` plugin outright: ``` The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0. ``` Opting out needs two flags together, `android.builtInKotlin=false` and `android.newDsl=false`, because the kotlin-android plugin casts the android extension to `BaseExtension`, which the new DSL drops. #3964 took that opt-out so the version bump stayed reviewable. AGP 10 removes the opt-out, so the migration has to happen regardless; doing it separately keeps the two diffs legible. ### Description Four things blocked the migration, each hidden behind the previous. **The old DSL extension types are gone.** `com.android.build.gradle.BaseExtension` and `com.android.build.gradle.LibraryExtension` do not exist once the new DSL is active. The convention plugins now configure `com.android.build.api.dsl.ApplicationExtension` and `com.android.build.api.dsl.LibraryExtension`, and set `compileSdk` directly rather than calling `compileSdkVersion()`. `KotlinAndroidProjectExtension` stays as is: AGP's built-in Kotlin still registers it. **safeargs 2.5.3 cannot see AGP 9.** It fails with `safeargs plugin must be used with android plugin`, because its detection goes through the old DSL. The 2.5.3 jar references `com/android/build/gradle/BaseExtension`, `AppExtension` and `gradle/api/BaseVariant`; the 2.9.8 jar references only `com.android.build.api.variant.*`. `examples/purchase-tester` is the only module applying the plugin, and it gets its own commit. **Dokka source sets changed shape.** With the kotlin-android plugin, Dokka saw the Kotlin source sets and the config addressed `main`, `defaults` and `customEntitlementComputation`. Built-in Kotlin instead registers one source set per Android variant, and registers them after the `dokka { }` block runs, so `named("defaultsRelease")` throws even though the name is valid later. Both modules now select through `matching { }.configureEach`. `:purchases` keeps `reportUndocumented`, `skipDeprecated`, the Android external documentation link, both source links and the `paywalls.components` suppression, all moved onto the selected variant. The explicit `customEntitlementComputation` suppression is load-bearing, not redundant: Dokka defaults `suppress` to `!isPublishable` and `customEntitlementComputationRelease` is published, so it would otherwise be documented. **`kotlin-test` stops carrying JUnit.** `:feature:galaxy` names `kotlin-test-junit` instead. Verified rather than inherited: with plain `kotlin-test`, `org.junit.Test` is unresolved across all 25 test methods. **Not visible in the diff:** `./gradlew :dokkaGenerate` fails on the base commit with `Dokka cannot generate documentation for Android projects with multiple enabled variants that have common source roots`. The `docs-deploy` job that runs it is gated on release tags, so #3964 never exercised it. This PR is what makes it pass again, which is a bigger deal than the diff makes it look. **Worth noting separately:** AGP 9.2.1 declares `kotlin-gradle-plugin:2.2.10`, so built-in Kotlin takes its compiler from buildscript classpath resolution rather than from our catalog. Confirmed the published contract from #3964 is intact rather than assumed: | check | result | | --- | --- | | resolved KGP | `2.2.21`, not AGP's bundled `2.2.10` | | `:purchases` POM | `kotlin-stdlib:2.2.21` at `compile` scope | | release AAR metadata | `mv=[1,8,0]`, so `languageVersion` 1.8 survives | ### Regression gates - `./gradlew :dokkaGenerate` for the Dokka rework. Generates 1367 pages with `com.revenuecat.purchases.paywalls.components` still suppressed. - `:feature:galaxy:testDefaultsDebugUnitTest` for the `kotlin-test-junit` swap. Needs `--rerun-tasks`: a cached run passes even with the dependency reverted, which is how this could slip through. - `:examples:purchase-tester:assembleDebug` for the navigation bump. **Rejected:** - Wiring the Dokka variant selector to `ANDROID_VARIANT_TO_PUBLISH` instead of hardcoding `"defaultsRelease"` twice. `fastlane/Fastfile:295` rewrites that property per publish, so a `customEntitlementComputation` publish would point Dokka at exactly the variant `:purchases` suppresses. - Hoisting the shared Dokka config into `ConfigureDokka.kt`. Dokka's types are not on build-logic's compile classpath, so it needs the reflection pattern from `ConfigureMetalava.kt`, and sharing `reportUndocumented.set(true)` would newly turn it on for the four public-library modules that have no `dokka { }` block. - Moving the five hand-rolled app modules onto `revenuecat-android-application`, which would collapse 5 of the 6 per-module edits. Each keeps 2 to 3 overrides, and both `testpurchases*` modules deliberately pin old `compileSdk`/`targetSdk` to test compatibility. Worth a separate PR, not this one. **Limitations:** - `examples/MagicWeather` and `test-apps/sdksizetesting` still apply `kotlin-android`. They are separate builds pinned to AGP 8.13.2, so they keep the plugin correctly and are untouched. - `purchases/build.gradle.kts:120` has a dead `androidComponents { onVariants }` block guarding a `"free"` product flavor that does not exist in the repo. Pre-existing and unrelated, left alone. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Wide Gradle/AGP convention changes affect every Android module’s compile and doc generation; navigation and Dokka behavior are prerequisites for CI release docs, with limited runtime SDK impact. > > **Overview** > Completes the AGP 9 migration by removing the **`android.builtInKotlin=false`** / **`android.newDsl=false`** opt-outs and dropping **`org.jetbrains.kotlin.android`** across convention plugins, app modules, samples, and the version catalog. > > Build-logic now configures **`ApplicationExtension`** / **`LibraryExtension`** from the new DSL (e.g. **`compileSdk`** instead of **`BaseExtension`**). **`:purchases`** and **`:ui:revenuecatui`** Dokka blocks use **`matching { }.configureEach`** so documentation targets **`defaultsRelease`** and suppresses **`customEntitlementComputation`** variants after AGP registers per-variant source sets lazily. > > **`androidx.navigation`** is bumped **2.5.3 → 2.9.8** so Safe Args works with the new DSL. **`:feature:galaxy`** switches unit tests to **`kotlin-test-junit`**. **`ui/debugview/api.txt`** drops **`InternalDebugRevenueCatScreenViewModelFactory`** (debug-only; not in the release API Metalava analyzes). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e45c589. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
> [!NOTE] > This targets branch `11.0-dev` which will become the epic branch for the upcoming major release. Once this first PR is merged, I'll open a continuous one from that one to `main` until we are ready to release it altogether. - Moves the SDK's build toolchain to AGP 9: | Dependency | From | To | |---|---|---| | AGP | 8.13.2 | **9.2.1** | | Gradle | 8.14.5 | **9.4.1** | | Kotlin | 2.0.21 | **2.2.21** | | Poko | 0.17.2 | **0.20.2** | | Paparazzi | 2.0.0-alpha02 | **2.0.0-alpha05** | - No public API change and no `api*.txt` diff. - **Why this becomes a major**: this transitively raises the miminum kotlin version for consumers to 2.1.0. up from 1.8.0. - Kotlin lands on **2.2.21, the minimum required by AGP 9**, - AGP is capped at **9.2.1** rather than latest: AGP 9.3.x requires Gradle 9.5+, and Gradle 9.5+ embeds a Kotlin that a 2.2 compiler cannot read, bumping consumers to an even higher minimum Kotlin version. - Nothing downstream needs AGP 9 or Gradle 9, just the Kotlin bump. The published AAR metadata is unchanged (`minCompileSdk=1`, `minAndroidGradlePluginVersion=1.0.0`). Companion docs PR: RevenueCat/docs#1942 Companion PHC PR: RevenueCat/purchases-hybrid-common#1844 - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> AGP 8 is increasingly behind, and AGP 9 removes a number of DSL surfaces we still used. The prerequisite work already landed separately in #3931, #3961 and #3963. AGP 9 has a runtime dependency on Kotlin Gradle Plugin **2.2.10 or higher**, so a Kotlin bump is not optional here. Everything above that floor is a choice, and this PR deliberately takes the minimum. An earlier revision of this branch used Kotlin 2.3.21. Measuring the cost changed the decision: - **It breaks every React Native consumer.** We publish `kotlin-stdlib` at `compile` scope and Gradle resolves consumers onto it. A compiler reads metadata at most one minor ahead. Kotlin 2.3.x publishes metadata 2.3.0, and React Native's own Gradle plugin pins the compiler: 2.0.21 through RN 0.79, 2.1.20 through RN 0.86, with only RN `main` on 2.2.0. Reproduced on a real RN 0.86 app: `metadata version is 2.3.0 / compiler version 2.1.0 can read versions up to 2.2.0`. Every RN app would have needed an explicit `kotlin-gradle-plugin` version in `android/build.gradle`. - **It forces the K2 migration.** Kotlin 2.3.21 rejects `languageVersion 1.8` outright. 2.2.x still accepts it, so the frontend migration stays out of a version bump where it does not belong. - **It does not avoid a second bump anyway.** Kotlin 2.4 removes `languageVersion 1.8` regardless. At Kotlin 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched. This is the non-obvious constraint, and CI found it rather than local builds. `:codegen` is a `java-gradle-plugin` module, so it compiles against the Kotlin that **Gradle itself embeds**. Embedded Kotlin by Gradle release: | Gradle | Embedded Kotlin | Readable by a 2.2 compiler (max 2.3.0) | |---|---|---| | 9.3.1 | 2.2.21 | yes | | 9.4.1 | 2.3.0 | yes, exactly at the limit | | 9.5.1 | 2.3.20 | no | | 9.7.0 | 2.4.0 | no | Gradle 9.7.0 therefore failed `prepare-tests` with `metadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0`. AGP 9.3.x requires Gradle 9.5.0 or higher (`Minimum supported Gradle version is 9.5.0`), which is exactly the range we cannot read. **AGP 9.2.1 is the newest release that runs on Gradle 9.4.1**; 9.1.1 and 9.0.1 were also confirmed to work, 9.3.1 was confirmed not to. So the AGP ceiling here is a consequence of the Kotlin floor, not an independent preference. When the deferred Kotlin 2.3 bump happens, AGP and Gradle can move up with it. - Standalone sample apps (`CustomEntitlementComputationSample`, `MagicWeather`, `MagicWeatherCompose`) move to Kotlin 2.2.21 and gain the `compose-compiler` plugin alias. They consume our published artifacts, so they need a compiler that can read our metadata. - Unit tests are re-enabled on the release variant via `HasUnitTestBuilder.enableUnitTest`, because AGP 9 only creates unit tests for the `testBuildType` variant and the published variant is release. - The `androidDependencies` cache-warm CI step is dropped; AGP 9 removed that task. - `migrations/v11-MIGRATION.md` documents the new Kotlin floor, mirroring the v9 guide. - README `Requirements` now says Kotlin 2.1.0+. - **Kotlin 2.3 and the K1 to K2 migration**, until React Native ships the KGP 2.2 pin already on its `main` branch. Doing it sooner buys nothing and costs every RN app a migration step. - **AGP 9 built-in Kotlin** (previously #3965). Not breaking and not required; the `android.builtInKotlin=false` / `android.newDsl=false` opt-out only has to go at AGP 10. - `assembleDebug` across all modules; release AARs for `:purchases` and `:ui:revenuecatui`. - `:purchases` unit tests, `:ui:debugview:verifyPaparazziDefaultsDebug`, `detektAll`. - `scripts/api-check.sh` with **zero** `api*.txt` diff. - Published contract checked directly: POM carries `kotlin-stdlib:2.2.21` at `compile` scope, the release AAR still reports `minCompileSdk=1` and `minAndroidGradlePluginVersion=1.0.0`, and our own classes carry metadata `mv = {1, 8, 0}`. - Consumer floor measured against the real release AAR plus the stdlib we publish: Kotlin 2.1.21 compiles; 2.0.21 and 1.9.24 fail. That is what the 2.1.0 claim in the README rests on. The Kotlin floor is what propagates, not AGP. Nothing downstream needs AGP 9 or Gradle 9. The only direct consumer is purchases-hybrid-common, which re-exposes us at compile scope via `api(libs.purchases)`. RevenueCat/purchases-hybrid-common#1844 raises its Kotlin accordingly, and the hybrid SDKs pick the floor up from there rather than from this repo. Documentation follows separately: - RevenueCat/docs#1942 adds the Kotlin minimum to the Android, Flutter and React Native installation pages, and adds the 9.x-to-10.x and 10.x-to-11.x migration guides the site was missing. - RevenueCat/purchases-flutter#1876 states the Kotlin minimum in that repo's README. Flutter applies the version from the app's `android/settings.gradle`, so the requirement lands on the consuming app and the Flutter version itself does not move. - RevenueCat/react-native-purchases#1925 raises the React Native floor to 0.80.0, the first release whose Gradle plugin pins a Kotlin that can read our metadata. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Major toolchain and minimum Kotlin version changes affect all consumers and CI, though public API and published AAR AGP/minSdk metadata are intentionally unchanged. > > **Overview** > This PR moves the SDK build to **AGP 9.2.1**, **Gradle 9.4.1**, and **Kotlin 2.2.21**, and bumps related tooling (Poko, Paparazzi, baseline profile plugin). **`gradle.properties`** opts out of AGP 9’s built-in Kotlin and new DSL for now. > > The **consumer-facing change** is a higher Kotlin floor: README and **`migrations/v11-MIGRATION.md`** document **Kotlin 2.1.0+** (up from 1.8.0), driven by published stdlib metadata—not a public API change. > > **Build-logic** re-enables unit tests on library variants under AGP 9 via **`enableUnitTest`**, and keeps **`aarMetadata.minCompileSdk = 1`** so published AAR requirements don’t tighten. > > Compose sample apps switch to the **Kotlin Compose compiler plugin** and drop **`composeOptions.kotlinCompilerExtensionVersion`**. Several modules replace deprecated **`kotlinOptions`** with **`kotlin.compilerOptions.jvmTarget`**. > > **CircleCI** drops the removed **`androidDependencies`** warmup and uses Gradle cache restore on standalone sample builds instead. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b6b4df. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Removes the `android.builtInKotlin=false` and `android.newDsl=false` opt-out that #3964 added, from the root build and from both standalone sample builds, and takes `kotlin-android` out of 11 build scripts and the 3 convention plugins. - `ui/debugview/api.txt` loses `InternalDebugRevenueCatScreenViewModelFactory`. Metalava now analyses the release variant, and that class lives in `src/debug/kotlin`. The release AAR contains zero occurrences of it, so it never shipped and the entry was wrong - Fixes `:dokkaGenerate`, which is already broken on `11.0-dev`. Built-in Kotlin registers Dokka source sets per Android variant instead of per Kotlin source set, and registers them lazily, so both `:purchases` and `:ui:revenuecatui` now select the documented variant through `configureEach`. - Bumps `androidx.navigation` 2.5.3 to 2.9.8. safeargs 2.5.3 detects AGP through the old DSL and hard fails, so the navigation bump is a prerequisite for the new DSL rather than an independent upgrade. ### Checklist - [ ] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids <details><summary>Agent description</summary> ### Motivation AGP 9 enables built-in Kotlin and rejects the `org.jetbrains.kotlin.android` plugin outright: ``` The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin support since AGP 9.0. ``` Opting out needs two flags together, `android.builtInKotlin=false` and `android.newDsl=false`, because the kotlin-android plugin casts the android extension to `BaseExtension`, which the new DSL drops. #3964 took that opt-out so the version bump stayed reviewable. AGP 10 removes the opt-out, so the migration has to happen regardless; doing it separately keeps the two diffs legible. ### Description Four things blocked the migration, each hidden behind the previous. **The old DSL extension types are gone.** `com.android.build.gradle.BaseExtension` and `com.android.build.gradle.LibraryExtension` do not exist once the new DSL is active. The convention plugins now configure `com.android.build.api.dsl.ApplicationExtension` and `com.android.build.api.dsl.LibraryExtension`, and set `compileSdk` directly rather than calling `compileSdkVersion()`. `KotlinAndroidProjectExtension` stays as is: AGP's built-in Kotlin still registers it. **safeargs 2.5.3 cannot see AGP 9.** It fails with `safeargs plugin must be used with android plugin`, because its detection goes through the old DSL. The 2.5.3 jar references `com/android/build/gradle/BaseExtension`, `AppExtension` and `gradle/api/BaseVariant`; the 2.9.8 jar references only `com.android.build.api.variant.*`. `examples/purchase-tester` is the only module applying the plugin, and it gets its own commit. **Dokka source sets changed shape.** With the kotlin-android plugin, Dokka saw the Kotlin source sets and the config addressed `main`, `defaults` and `customEntitlementComputation`. Built-in Kotlin instead registers one source set per Android variant, and registers them after the `dokka { }` block runs, so `named("defaultsRelease")` throws even though the name is valid later. Both modules now select through `matching { }.configureEach`. `:purchases` keeps `reportUndocumented`, `skipDeprecated`, the Android external documentation link, both source links and the `paywalls.components` suppression, all moved onto the selected variant. The explicit `customEntitlementComputation` suppression is load-bearing, not redundant: Dokka defaults `suppress` to `!isPublishable` and `customEntitlementComputationRelease` is published, so it would otherwise be documented. **`kotlin-test` stops carrying JUnit.** `:feature:galaxy` names `kotlin-test-junit` instead. Verified rather than inherited: with plain `kotlin-test`, `org.junit.Test` is unresolved across all 25 test methods. **Not visible in the diff:** `./gradlew :dokkaGenerate` fails on the base commit with `Dokka cannot generate documentation for Android projects with multiple enabled variants that have common source roots`. The `docs-deploy` job that runs it is gated on release tags, so #3964 never exercised it. This PR is what makes it pass again, which is a bigger deal than the diff makes it look. **Worth noting separately:** AGP 9.2.1 declares `kotlin-gradle-plugin:2.2.10`, so built-in Kotlin takes its compiler from buildscript classpath resolution rather than from our catalog. Confirmed the published contract from #3964 is intact rather than assumed: | check | result | | --- | --- | | resolved KGP | `2.2.21`, not AGP's bundled `2.2.10` | | `:purchases` POM | `kotlin-stdlib:2.2.21` at `compile` scope | | release AAR metadata | `mv=[1,8,0]`, so `languageVersion` 1.8 survives | ### Regression gates - `./gradlew :dokkaGenerate` for the Dokka rework. Generates 1367 pages with `com.revenuecat.purchases.paywalls.components` still suppressed. - `:feature:galaxy:testDefaultsDebugUnitTest` for the `kotlin-test-junit` swap. Needs `--rerun-tasks`: a cached run passes even with the dependency reverted, which is how this could slip through. - `:examples:purchase-tester:assembleDebug` for the navigation bump. **Rejected:** - Wiring the Dokka variant selector to `ANDROID_VARIANT_TO_PUBLISH` instead of hardcoding `"defaultsRelease"` twice. `fastlane/Fastfile:295` rewrites that property per publish, so a `customEntitlementComputation` publish would point Dokka at exactly the variant `:purchases` suppresses. - Hoisting the shared Dokka config into `ConfigureDokka.kt`. Dokka's types are not on build-logic's compile classpath, so it needs the reflection pattern from `ConfigureMetalava.kt`, and sharing `reportUndocumented.set(true)` would newly turn it on for the four public-library modules that have no `dokka { }` block. - Moving the five hand-rolled app modules onto `revenuecat-android-application`, which would collapse 5 of the 6 per-module edits. Each keeps 2 to 3 overrides, and both `testpurchases*` modules deliberately pin old `compileSdk`/`targetSdk` to test compatibility. Worth a separate PR, not this one. **Limitations:** - `examples/MagicWeather` and `test-apps/sdksizetesting` still apply `kotlin-android`. They are separate builds pinned to AGP 8.13.2, so they keep the plugin correctly and are untouched. - `purchases/build.gradle.kts:120` has a dead `androidComponents { onVariants }` block guarding a `"free"` product flavor that does not exist in the repo. Pre-existing and unrelated, left alone. </details> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Wide Gradle/AGP convention changes affect every Android module’s compile and doc generation; navigation and Dokka behavior are prerequisites for CI release docs, with limited runtime SDK impact. > > **Overview** > Completes the AGP 9 migration by removing the **`android.builtInKotlin=false`** / **`android.newDsl=false`** opt-outs and dropping **`org.jetbrains.kotlin.android`** across convention plugins, app modules, samples, and the version catalog. > > Build-logic now configures **`ApplicationExtension`** / **`LibraryExtension`** from the new DSL (e.g. **`compileSdk`** instead of **`BaseExtension`**). **`:purchases`** and **`:ui:revenuecatui`** Dokka blocks use **`matching { }.configureEach`** so documentation targets **`defaultsRelease`** and suppresses **`customEntitlementComputation`** variants after AGP registers per-variant source sets lazily. > > **`androidx.navigation`** is bumped **2.5.3 → 2.9.8** so Safe Args works with the new DSL. **`:feature:galaxy`** switches unit tests to **`kotlin-test-junit`**. **`ui/debugview/api.txt`** drops **`InternalDebugRevenueCatScreenViewModelFactory`** (debug-only; not in the release API Metalava analyzes). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e45c589. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->



Note
This targets branch
11.0-devwhich will become the epic branch for the upcoming major release. Once this first PR is merged, I'll open a continuous one from that one tomainuntil we are ready to release it altogether.api*.txtdiff.minCompileSdk=1,minAndroidGradlePluginVersion=1.0.0).Companion docs PR: https://github.com/RevenueCat/docs/pull/1942
Companion PHC PR: RevenueCat/purchases-hybrid-common#1844
Checklist
purchases-iosand hybridsAgent description
Motivation
AGP 8 is increasingly behind, and AGP 9 removes a number of DSL surfaces we still used. The
prerequisite work already landed separately in #3931, #3961 and #3963.
AGP 9 has a runtime dependency on Kotlin Gradle Plugin 2.2.10 or higher, so a Kotlin bump is not
optional here. Everything above that floor is a choice, and this PR deliberately takes the minimum.
Why not Kotlin 2.3.x
An earlier revision of this branch used Kotlin 2.3.21. Measuring the cost changed the decision:
kotlin-stdlibatcompilescope and Gradleresolves consumers onto it. A compiler reads metadata at most one minor ahead. Kotlin 2.3.x
publishes metadata 2.3.0, and React Native's own Gradle plugin pins the compiler: 2.0.21 through
RN 0.79, 2.1.20 through RN 0.86, with only RN
mainon 2.2.0. Reproduced on a real RN 0.86 app:metadata version is 2.3.0 / compiler version 2.1.0 can read versions up to 2.2.0. Every RN appwould have needed an explicit
kotlin-gradle-pluginversion inandroid/build.gradle.languageVersion 1.8outright. 2.2.x stillaccepts it, so the frontend migration stays out of a version bump where it does not belong.
languageVersion 1.8regardless.At Kotlin 2.2.21 the published metadata is 2.2.0, which RN 0.81+ reads untouched.
Why AGP 9.2.1 and Gradle 9.4.1
This is the non-obvious constraint, and CI found it rather than local builds.
:codegenis ajava-gradle-pluginmodule, so it compiles against the Kotlin that Gradle itselfembeds. Embedded Kotlin by Gradle release:
Gradle 9.7.0 therefore failed
prepare-testswithmetadata version is 2.4.0, but the compiler version 2.2.0 can read versions up to 2.3.0.AGP 9.3.x requires Gradle 9.5.0 or higher (
Minimum supported Gradle version is 9.5.0), which isexactly the range we cannot read. AGP 9.2.1 is the newest release that runs on Gradle 9.4.1;
9.1.1 and 9.0.1 were also confirmed to work, 9.3.1 was confirmed not to.
So the AGP ceiling here is a consequence of the Kotlin floor, not an independent preference. When the
deferred Kotlin 2.3 bump happens, AGP and Gradle can move up with it.
Also in this PR
CustomEntitlementComputationSample,MagicWeather,MagicWeatherCompose)move to Kotlin 2.2.21 and gain the
compose-compilerplugin alias. They consume our publishedartifacts, so they need a compiler that can read our metadata.
HasUnitTestBuilder.enableUnitTest, becauseAGP 9 only creates unit tests for the
testBuildTypevariant and the published variant is release.androidDependenciescache-warm CI step is dropped; AGP 9 removed that task.migrations/v11-MIGRATION.mddocuments the new Kotlin floor, mirroring the v9 guide.Requirementsnow says Kotlin 2.1.0+.Deferred on purpose
mainbranch. Doing it sooner buys nothing and costs every RN app a migration step.android.builtInKotlin=false/android.newDsl=falseopt-out only has to go at AGP 10.Testing
assembleDebugacross all modules; release AARs for:purchasesand:ui:revenuecatui.:purchasesunit tests,:ui:debugview:verifyPaparazziDefaultsDebug,detektAll.scripts/api-check.shwith zeroapi*.txtdiff.kotlin-stdlib:2.2.21atcompilescope, therelease AAR still reports
minCompileSdk=1andminAndroidGradlePluginVersion=1.0.0, and our ownclasses carry metadata
mv = {1, 8, 0}.compiles; 2.0.21 and 1.9.24 fail. That is what the 2.1.0 claim in the README rests on.
Downstream
The Kotlin floor is what propagates, not AGP. Nothing downstream needs AGP 9 or Gradle 9.
The only direct consumer is purchases-hybrid-common, which re-exposes us at compile scope via
api(libs.purchases). RevenueCat/purchases-hybrid-common#1844 raises its Kotlin accordingly, and thehybrid SDKs pick the floor up from there rather than from this repo.
Documentation follows separately:
pages, and adds the 9.x-to-10.x and 10.x-to-11.x migration guides the site was missing.
the version from the app's
android/settings.gradle, so the requirement lands on the consuming appand the Flutter version itself does not move.
whose Gradle plugin pins a Kotlin that can read our metadata.
Note
Medium Risk
Major toolchain and minimum Kotlin version changes affect all consumers and CI, though public API and published AAR AGP/minSdk metadata are intentionally unchanged.
Overview
This PR moves the SDK build to AGP 9.2.1, Gradle 9.4.1, and Kotlin 2.2.21, and bumps related tooling (Poko, Paparazzi, baseline profile plugin).
gradle.propertiesopts out of AGP 9’s built-in Kotlin and new DSL for now.The consumer-facing change is a higher Kotlin floor: README and
migrations/v11-MIGRATION.mddocument Kotlin 2.1.0+ (up from 1.8.0), driven by published stdlib metadata—not a public API change.Build-logic re-enables unit tests on library variants under AGP 9 via
enableUnitTest, and keepsaarMetadata.minCompileSdk = 1so published AAR requirements don’t tighten.Compose sample apps switch to the Kotlin Compose compiler plugin and drop
composeOptions.kotlinCompilerExtensionVersion. Several modules replace deprecatedkotlinOptionswithkotlin.compilerOptions.jvmTarget.CircleCI drops the removed
androidDependencieswarmup and uses Gradle cache restore on standalone sample builds instead.Reviewed by Cursor Bugbot for commit 1b6b4df. Bugbot is set up for automated code reviews on this repo. Configure here.