fix(paywalls): Expose compose-foundation-layout so PaywallFooter is callable without extra setup - #3961
Conversation
PaywallFooter, OriginalTemplatePaywallFooter and CloseButton expose PaddingValues and BoxScope in their signatures, but every Compose dependency was declared as implementation, so consumers never got foundation-layout on their compile classpath. Kotlin 2.0.x tolerated the unresolvable type; 2.2 rejects it, so consumers on a recent Kotlin cannot call those APIs unless they happen to pull foundation-layout in themselves. Export the Compose BOM alongside it: without the BOM in the api variant the published metadata carries no version for foundation-layout.
| dependencies { | ||
| api(project(":purchases")) | ||
| api(platform(libs.compose.bom)) | ||
| api(libs.compose.foundation.layout) |
There was a problem hiding this comment.
[Not related to this line]
Hmm so exposing those types in our API was a mistake 😅 This means that we are now tied to the compose API, and if they have breaking changes, we would need to do a major as well theoretically :(
In my mind the appropriate fix would be to, since we need to do a major. Change the public API of the PaywallFooter types to avoid exposing any compose types + hide (make internal) the CloseButton that seems like something we shouldn't have ever exposed. Wdyt @AlvaroBrey @JayShortway?
There was a problem hiding this comment.
Don't we expose a bunch of Compose types in the ui module anyway? I don't think these are the only ones by far (and also it's a compose-aware UI lib, we are kinda supposed to?)
There was a problem hiding this comment.
Well... we do expose annotations like @Composable or @Immutable and some types like FontFamily... It's not great since it means that, if we want to update compose, and that update includes a breaking change, we could potentially be breaking someone that might be using an older version of compose, so not ideal...
But yeah, you do have a good point, and I don't have a good thought on how to workaround those annotations and types we already expose... So with that in mind, I guess I'm ok with this then. 👍
There was a problem hiding this comment.
if we want to update compose, and that update includes a breaking change, we could potentially be breaking someone that might be using an older version of compose
I think this is true regardless of implementation/api, because we will pull in the newer compose version and that will win over the older version the app has declared (in a standard setup).
It's the most correct (tm) to declare Compose as api indeed because it is part of our public API. One question: do we need to make the bom api too, or is foundation.layout enough?
There was a problem hiding this comment.
Hmm you're right... so yeah please ignore me 😅 🙇 . Not sure if we need to also api the bom TBH... But in the PR description:
Without it the published Gradle metadata lists foundation-layout with no version in the api
variant, so a consumer without their own Compose BOM could not resolve it.
So it seems it is?
There was a problem hiding this comment.
Right, for the reasons above we need to either set the bom as api, or set a explicit version for foundation-layout
> [!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 -->
> [!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 -->
> [!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 -->
> [!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 -->
> [!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 -->

PaywallFooter,OriginalTemplatePaywallFooterandCloseButtonexposePaddingValuesandBoxScopein their signatures, but every Compose dependency was declaredimplementation, so consumers never receivedfoundation-layouton their compile classpath.foundation-layoutin themselves. Most Compose apps do, viafoundationormaterial, which is why this went unnoticed.apias well. Without it the published Gradle metadata listsfoundation-layoutwith no version in the api variant, so a consumer without their own Compose BOM could not resolve it.dependencyManagement, so this aligns Gradle with existing Maven behavior rather than adding a new constraint.scripts/api-check.shreports no signature diff.foundation-layoutonapi-tester's compile classpath goes from absent to resolving at 1.7.2, withapi-testerdeclaring nothing.Checklist
purchases-iosand hybridsAgent description
Motivation
:ui:revenuecatuideclared every Compose dependency asimplementation, withapi(project(":purchases"))as the only exported dependency. Three public declarations nevertheless reference
androidx.compose.foundation.layouttypes:A consumer therefore needs
foundation-layouton its own compile classpath to call them, even thoughnothing in the published metadata says so.
Removing the workaround from
api-testerand compiling reproduces it:Note it fails on the overloads that pass no lambda at all, because the compiler still resolves the
default parameter's type.
The consumer compiles with their own Kotlin, not ours, so this is not caused by the SDK's toolchain:
anyone on Kotlin 2.2 or newer is already affected by shipped versions. It surfaced here only because
api-testeris an unusually minimal consumer, pullingcompose-ui,activity-composeandcompose-ui-google-fontsbut neitherfoundationnormaterial, both of which would have draggedfoundation-layoutin transitively.Description
api(libs.compose.foundation.layout)in:ui:revenuecatui, plus a catalog entry for it.api(platform(libs.compose.bom)), replacing theimplementationdeclaration of the same platform.This is required, not incidental. With the BOM left at
implementation, the generated POM is fine,because the BOM is already published as a
dependencyManagementimport and Maven resolves theversion from it, but the Gradle module metadata api variant comes out as:
with no accompanying platform dependency or constraint. Gradle consumers are the majority here, so
the POM alone being correct is not enough.
No source changes and no signature changes.
Testing
foundation-layoutin:api-tester'sdefaultsDebugCompileClasspath: absent before, resolves to1.7.2 after, without
api-testerdeclaring it.module.jsonbefore and after. The POM gainsfoundation-layoutatcompilescope; the api variant gains bothcompose-bom:2024.09.00andfoundation-layout.assembleDefaultsRelease,:api-tester:compileDefaultsDebugKotlin,detektAll, andscripts/api-check.shall pass locally.api-testerdeclaresno Compose layout dependency of its own,
:api-tester:compileDefaultsDebugKotlinsucceeds. That isthe exact compilation that fails without this change.
api-testeris the lasting regression gate: it exercises these call sites, and under Kotlin 2.2 itcannot compile unless this dependency is exported.
Related
api-testerworkaround forthe same problem; that commit is dropped now that the dependency is exported properly, which is also
the end to end check described above.