Skip to content

build: prepare for the purchases-android v11 Kotlin floor - #1925

Draft
AlvaroBrey wants to merge 5 commits into
mainfrom
build/kotlin-floor-prep
Draft

build: prepare for the purchases-android v11 Kotlin floor#1925
AlvaroBrey wants to merge 5 commits into
mainfrom
build/kotlin-floor-prep

Conversation

@AlvaroBrey

@AlvaroBrey AlvaroBrey commented Aug 26, 2026

Copy link
Copy Markdown
Contributor
  • Prepares this repo for the purchases-android v11 Kotlin floor, which arrives here via build: update Kotlin to 2.2.20 purchases-hybrid-common#1844. The published kotlin-stdlib metadata becomes 2.2.0, and a Kotlin compiler reads metadata at most one minor ahead, so anything compiling our Android modules needs Kotlin 2.1 or newer.
  • Consumer-facing floor: this raises the minimum supported React Native to 0.80. RN pins the Kotlin compiler that builds native modules, so an app's RN version decides whether it can read our metadata. Measured at every tag: 0.78.x and 0.79.x pin KGP 2.0.21 and fail; 0.80.x through 0.86 pin 2.1.20 and work. This belongs in the release notes for this repo, not in the purchases-android ones.
  • Raises the fallback Kotlin versions used when an app does not set rootProject.ext.kotlinVersion: Purchases_kotlinVersion 1.8.22 and PurchasesUi_kotlinVersion 1.7.21 both become 2.2.20. Apps that do set it are unaffected by this half.
  • Upgrades purchaseTesterTypescript and MaestroTestApp from React Native 0.78 to 0.86. Not cosmetic: the app's React Native version decides the compiler, and RN 0.78 pins KGP 2.0.21, which cannot read metadata 2.2.0.
  • The upgrade could not be avoided by pinning. Forcing KGP 2.1.20 or 2.2.20 on RN 0.78 fails with Found interface KotlinTopLevelExtension, but class was expected, because its Gradle plugin is built against 2.0.21.
  • These two apps are also the only ones that include react-native-purchases-ui, so without this the UI module has no build coverage under the new floor.
  • Most of the diff is yarn.lock. The hand written change is 6 files.

Checklist

  • A description about what and why you are contributing
  • The issue number(s) or PR number(s) in the description
  • If applicable, unit tests
Agent description

Motivation

purchases-android v11 moves to Kotlin 2.2.21, purchases-hybrid-common follows, and both publish
kotlin-stdlib at compile scope. Gradle resolves consumers onto the highest version, and a
compiler reads metadata from at most one minor ahead, so the effective requirement for compiling
against them becomes Kotlin 2.1.

React Native is the only SDK in the suite where this is not a simple version bump, because our
modules do not control their own compiler:

def kotlin_version = rootProject.ext.has('kotlinVersion')
    ? rootProject.ext.get('kotlinVersion')
    : project.properties['Purchases_kotlinVersion']

and the modules declare the Kotlin Gradle plugin classpath without a version, so React Native's own
plugin pins it. Measured from @react-native/gradle-plugin:

React Native Pins KGP Reads metadata up to
0.86, 0.83, 0.81 2.1.20 2.2.0
0.79, 0.78 2.0.21 2.1.0

So RN 0.81 and newer are fine, and RN 0.78 is not.

Why the apps had to be upgraded rather than pinned

Forcing a newer KGP on RN 0.78 was tried first and fails at configuration time:

Found interface org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension, but class was expected

This happens with both 2.1.20 and 2.2.20, because RN 0.78's Gradle plugin is compiled against KGP
2.0.21. The only way forward for those two apps is a React Native upgrade, so they move to 0.86 to
match MagicWeather and adsTester.

What is not in this PR

An explicit kotlinOptions.jvmTarget was tried on both modules, on the theory that KGP 2.2 is
stricter about jvmTarget and sourceCompatibility diverging. It breaks the build:

Inconsistent JVM-target compatibility detected for tasks
'compileReleaseJavaWithJavac' (17) and 'compileReleaseKotlin' (1.8).

React Native 0.86 runs javac at 17 and overrides the modules' declared sourceCompatibility 1.8,
so pinning Kotlin to 1.8 fights it. The targets align on their own, so nothing is set.

Nothing is changed in react-native-purchases-store-galaxy: it has no Kotlin sources.

Minimum React Native version

This is the consumer-visible consequence and the line to put in the release notes.

React Native's Gradle plugin pins the Kotlin compiler used to build native modules, so the app's
React Native version, not its own Kotlin setting, decides whether it can read our metadata. Read from
@react-native/gradle-plugin/gradle/libs.versions.toml at each tag:

React Native Pins KGP Reads metadata up to Against our 2.2.0
0.78.x, 0.79.x (checked .1, .3, .5, .7) 2.0.21 2.1.0 fails
0.80.x (checked .0, .1, .3) 2.1.20 2.2.0 works
0.81 through 0.86 2.1.20 2.2.0 works

Every 0.79 patch is 2.0.21 and every 0.80 patch is 2.1.20, so the boundary is exactly 0.80.0 rather
than some later 0.79 patch.

There is an unsupported escape hatch for apps below 0.80, verified to compile in a real RN 0.78 app:
adding -Xskip-metadata-version-check to the Kotlin compile tasks via allprojects in the app's
android/build.gradle suppresses the check and both modules build. It is not recommended and should
not be the documented path: it is an internal -X flag that silences a safety check rather than
teaching an older compiler the newer metadata, it requires injecting compiler arguments into modules
the app does not own, and only compilation was verified, not runtime behaviour.

Raising the Kotlin Gradle Plugin version instead does not work at all on those versions, see above.

Testing

Both published Android modules were built through both upgraded apps, which is the only meaningful
way to test them since rootProject.ext.kotlinVersion is what actually applies:

cd examples/purchaseTesterTypescript/android
./gradlew :react-native-purchases:assembleRelease :react-native-purchases-ui:assembleRelease

and the same under e2e-tests/MaestroTestApp/android. Both pass. All four example and test apps now
sit on kotlinVersion 2.1.20, matching what React Native 0.86 pins.

Related

purchases-android v11, and the purchases-hybrid-common release carrying
it, publish kotlin-stdlib with metadata 2.2.0. A Kotlin compiler reads
metadata at most one minor ahead, so anything compiling our Android
modules needs Kotlin 2.1 or newer.

Two parts:

- Raise the fallback Kotlin versions the modules use when an app does not
  set rootProject.ext.kotlinVersion: 1.8.22 and 1.7.21 both become
  2.2.20. Apps that do set it are unaffected.

- Upgrade purchaseTesterTypescript and MaestroTestApp from React Native
  0.78 to 0.86. This is not cosmetic: the app's React Native version
  decides the compiler, because the modules declare the Kotlin Gradle
  plugin classpath without a version and React Native's own plugin pins
  it. RN 0.78 pins KGP 2.0.21, which cannot read metadata 2.2.0, and it
  cannot simply be overridden: forcing KGP 2.1.20 or 2.2.20 on RN 0.78
  fails with "Found interface KotlinTopLevelExtension, but class was
  expected", because its plugin is built against 2.0.21. RN 0.86 pins
  2.1.20, which reads 2.2.0.

These two apps are also the only ones that include
react-native-purchases-ui, so without this the UI module has no build
coverage under the new floor.

All four example and test apps now sit on kotlinVersion 2.1.20.
The Kotlin floor this release introduces is enforced by React Native's
own Gradle plugin, which pins the compiler that builds native modules.
RN 0.78.x and 0.79.x pin KGP 2.0.21 and cannot read the 2.2.0 metadata
purchases-hybrid-common will publish; 0.80.x onward pin 2.1.20 and can.

Without this, npm installs happily onto an unsupported React Native and
the failure surfaces later as an opaque Kotlin metadata error at build
time rather than at install.

store-galaxy has no Kotlin sources of its own, so it is not directly
affected, but it is an add-on to react-native-purchases and ships in the
same release, so all three packages declare the same floor.
0.86 was overshoot. The requirement is only that the app's React Native
pins a Kotlin Gradle plugin able to read metadata 2.2.0, and 0.80 is the
first release that does: it pins KGP 2.1.20, same as 0.86.

Going to 0.86 dragged the apps past their other native dependencies.
react-native-screens 4.11 does not compile against 0.86, which removed
ShadowNode::Shared:

  RNSScreenShadowNode.h:31: error: no type named 'Shared' in
  'facebook::react::ShadowNode'

At 0.80.3 those dependencies stay in their supported range and both apps
build, native code included.
purchases-android and purchases-hybrid-common both land on 2.2.21, the
latest 2.2.x. These fallbacks were on 2.2.20 for no reason beyond an
earlier draft of the rollout plan.
The Requirements section still claimed React Native 0.73.0 and Kotlin
1.8.0. Both move with this release: React Native 0.80.0, because its
Gradle plugin pins the compiler, and Kotlin 2.1.0, because that is what
reads the metadata the SDK now publishes.
AlvaroBrey added a commit to RevenueCat/purchases-android that referenced this pull request Aug 27, 2026
> [!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 -->
AlvaroBrey added a commit to RevenueCat/purchases-android that referenced this pull request Aug 28, 2026
> [!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 -->
AlvaroBrey added a commit to RevenueCat/purchases-android that referenced this pull request Aug 31, 2026
> [!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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:breaking Changes that are breaking pr:other A code change that improves performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant