Skip to content

Commit 79de76a

Browse files
authored
[android][breaking] compile with kotlin 2.x and raise api / language version (#75)
## Summary Bumps the Kotlin compiler (and serialization plugin) from 1.9.23 to 2.3.21, and raises the consumer-floor pin (`apiVersion` / `languageVersion`) from Kotlin 1.9 to Kotlin 2.0. **This is a breaking change for consumers.** Raising `apiVersion` / `languageVersion` makes Kotlin 2.0+ the required consumer floor — anyone still building against 1.9 can't consume the new AAR. Bundling this into the next major release is deliberate (see below). First of a stack of PRs preparing the next major release. > **If you consume this library and you're stuck on Kotlin 1.9.x for any reason — please reach out before this lands.** We chose the lowest pin we believe the consumer base can absorb, but the data on real-world consumer floors is limited; concrete blockers will help us calibrate. ## Why now - **Kotlin 1.9 is deprecated by the 2.x compiler** and will be removed in a future minor. Holding the consumer pin at 1.9 forces us to stay on a 1.9.x compiler indefinitely, which is itself a dead-end path. - **One forced consumer-floor break, not two.** Doing the compiler bump and the `apiVersion`/`languageVersion` bump together means consumers cross the breaking boundary once. If we bumped just the compiler now and the pin later, consumers would absorb two separate "minimum Kotlin" raises in successive releases. - **Major-version cut is the cheapest moment to do it.** A consumer-floor raise is already a major-version event; bundling it into a planned major (alongside JVM 11) avoids spending a major version on this alone. - **Compiler ahead of the pin** (compiler 2.3, pin 2.0) leaves room for future routine compiler bumps without re-breaking consumers. ## Why pin 2.0 and not 2.1 We considered raising the pin to Kotlin 2.1 — it would silence a compiler warning we now carry (see below) and buy more runway before the next forced break. Pin 2.0 was the deliberate choice for consumer reach: - **React Native consumers**: the RN wrapper at `platforms/react-native/` claims `react-native: "*"` peer compatibility. RN templates ship Kotlin 1.9 on RN ≤ 0.76, Kotlin 2.0 on RN 0.77/0.78, Kotlin 2.1+ on RN 0.79+. Pin 2.0 keeps RN 0.77+ consumers reachable; pin 2.1 would cut off two RN minors that are still very common in production given how painful RN upgrades are. - **Unknown consumer toolchains**: we don't have systematic visibility into top consumers' Kotlin/AGP floors. Pin 2.0 is the most conservative non-deprecated choice available. ## Accepted trade-off: LV 2.0 deprecation warning in our build Kotlin 2.3's compiler now emits `Language version 2.0 is deprecated and its support will be removed in a future version of Kotlin. Update the version to 2.1.` on every `:lib:compile*Kotlin` invocation. We accept this warning rather than bump the pin to 2.1: - The warning is **in our build only** — consumers don't see it. They consume the published AAR; Kotlin metadata is what they read, and the metadata is valid for any Kotlin 2.0+ compiler. - It will become a hard error in some future Kotlin minor. When it does, we'll bump the pin in a minor release of the library, by which time the lagging RN/consumer cohort will have caught up and the cost will be lower. - No `lib.api` impact from the 2.3 compiler bump (verified — `apiDump` produced zero diff vs the 2.1.20 dump). ## Changes - `build.gradle`: Kotlin plugins (`org.jetbrains.kotlin.android`, `org.jetbrains.kotlin.plugin.serialization`) 1.9.23 → 2.3.21. - `lib/build.gradle`: `apiVersion` / `languageVersion` pinned to `KotlinVersion.KOTLIN_2_0`; explanatory comment updated. - `gradle.properties` (root + sample): AGP-opt-out comment block rewritten. `android.builtInKotlin=false` and `android.newDsl=false` remain, now documented as tooling-gap workarounds (BCV [#312](Kotlin/binary-compatibility-validator#312), [KT-71172](https://youtrack.jetbrains.com/issue/KT-71172)) rather than as Kotlin-version-specific. - `lib/api/lib.api`: regenerated. 780 / 780 line diff, all inside auto-generated kotlinx-serialization `$$serializer` companion classes flipping to `synthetic` with `final` methods (a language-2.0 codegen change, not a human-authored API change). - `CLAUDE.md`: references to the Kotlin pin updated 1.9 → 2.0. ## Why we're not on AGP 9's built-in Kotlin AGP 9 ships a built-in Kotlin plugin that replaces `org.jetbrains.kotlin.android`, but neither of our API-validation paths works with it for Android libraries today: - `binary-compatibility-validator` listens for the `kotlin-android` plugin id, which AGP's built-in path doesn't apply ([BCV #312](Kotlin/binary-compatibility-validator#312)) — `:lib:apiCheck`/`:lib:apiDump` silently never register, so our public-API gate disappears. - KGP's experimental `abiValidation` DSL exists for JVM and Multiplatform but is not yet exposed on `KotlinAndroidProjectExtension` ([KT-71172](https://youtrack.jetbrains.com/issue/KT-71172)). We stay on the explicit `org.jetbrains.kotlin.android` plugin so BCV keeps gating public API. The two AGP opt-outs (`builtInKotlin=false`, `newDsl=false`) are coupled to this decision — revisit when either upstream issue lands. Flipping them later is consumer-invisible: same `kotlinc` output, same `lib.api`. ## Consumer impact - **Consumer floor (breaking)**: Kotlin 2.0+ now required to consume the AAR. Consumers on 1.9.x toolchains can't build against the new release. Needs a prominent release-notes entry. - **React Native consumers**: RN 0.77+ apps (which ship Kotlin 2.0+) build fine. RN ≤ 0.76 (Kotlin 1.9) can't consume the new lib until they upgrade RN. - **Java consumers**: effectively unaffected by the Kotlin pin itself — Java doesn't read Kotlin metadata. Only transitive impact is `kotlin-stdlib` floor on the classpath rising from 1.9.x → 2.0.x. JVM target / JDK floor changes live in the stacked `target-jvm-11` PR. - **lib.api**: 780-line diff, all inside auto-generated kotlinx-serialization `$$serializer` companions — they flip to `synthetic` with `final` methods under language-2.0. No human-authored API moved. - **Practical day-to-day**: zero for normal consumers already on Kotlin 2.0+. `Json.encodeToString(...)`, `Foo.serializer()` and friends behave identically. Only theoretically affected: anyone subclassing an auto-generated `$$serializer` (not a supported kotlinx-serialization pattern). ## Test plan - [ ] `./gradlew :lib:clean :lib:test :lib:detekt :lib:lintRelease :lib:apiCheck :lib:assembleRelease` — green - [ ] `InteropTest` (Java consumer test) — all cases pass - [ ] Sample app `./gradlew assembleDebug` from `samples/MobileBuyIntegration/` — green ## Test plan - [ ] `./gradlew :lib:clean :lib:test :lib:detekt :lib:lintRelease :lib:apiCheck :lib:assembleRelease` — green - [ ] `InteropTest` (Java consumer test) — all cases pass - [ ] Sample app `./gradlew assembleDebug` from `samples/MobileBuyIntegration/` — green ## Test plan - [x] `./gradlew :lib:clean :lib:test :lib:detekt :lib:lintRelease :lib:apiCheck :lib:assembleRelease` passes locally. - [x] Java `InteropTest` passes (consumer-facing Java interop intact). - [x] `:lib:apiCheck` still gates public-API drift against `lib/api/lib.api`. - [ ] CI green on the PR. ### Before you merge > [!IMPORTANT] > > - [ ] I've added tests to support my implementation > - [x] I have read and agree with the [Contribution Guidelines](./CONTRIBUTING.md) > - [x] I have read and agree with the [Code of Conduct](./CODE_OF_CONDUCT.md) > - [x] I've updated the relevant platform README (`platforms/swift/README.md` and/or `platforms/android/README.md`) --- <details> <summary>Releasing a new Swift version?</summary> - [ ] I have bumped the version in `platforms/swift/ShopifyCheckoutKit.podspec` - [ ] I have bumped the version in `platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift` - [ ] I have updated `platforms/swift/CHANGELOG.md` - [ ] I have updated the SwiftPM/CocoaPods version snippets in `platforms/swift/README.md` (major version only) </details> <details> <summary>Releasing a new Android version?</summary> - [ ] I have bumped the `versionName` in `platforms/android/lib/build.gradle` - [ ] I have updated `platforms/android/CHANGELOG.md` - [ ] I have updated the Gradle/Maven version snippets in `platforms/android/README.md` </details> > [!TIP] > See the [Contributing documentation](./CONTRIBUTING.md) for the full release process per platform.
1 parent ec7bc62 commit 79de76a

6 files changed

Lines changed: 585 additions & 568 deletions

File tree

platforms/android/AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The sample is a separate Gradle composite (`samples/MobileBuyIntegration/setting
4343
- **`-Xexplicit-api=strict`** is on (`lib/build.gradle`). Every public class, method, field, and property must have an explicit visibility modifier. "Accidentally public" is not a thing here. This is a consumer-protection rule — if you see a public-by-default declaration, it was deliberate.
4444
- **Max line length: 140** (detekt-enforced). Detekt config: `lib/detekt.config.yml`.
4545
- **Library JVM target: 1.8.** Intentional for consumer compatibility; don't raise without a major-version discussion.
46-
- **Library Kotlin version is pinned.** The `lib/build.gradle` plugin version and any `apiVersion` / `languageVersion` settings exist to keep consumer compatibility stable. A Kotlin major-version migration is a planned major-version event, not a casual dep bump.
46+
- **Library Kotlin `apiVersion` / `languageVersion` are pinned at 2.0.** Set in `lib/build.gradle` so the AAR's bytecode stays consumable by Kotlin 2.0+ projects even though the compiler itself is on a newer 2.x. Bumping this pin is the consumer-facing breaking change, not bumping the compiler - treat it as a planned major-version event.
4747
- **Prefer generated protocol models.** Before adding hand-written protocol DTOs, check the generated models in `lib/src/main/java/com/shopify/checkoutkit/Models.kt` and the OpenRPC schema. Use generated UCP/ECP types for wire payloads; reserve local DTOs for Android-internal transport helpers that are not represented in the schema.
4848

4949
## Public API surface
@@ -91,6 +91,6 @@ Publishing goes through GitHub Releases → the repo-root `.github/workflows/and
9191

9292
## Things not to touch without discussion
9393

94-
- **Library Kotlin version pin.** Consumer compatibility floor; any migration is a deliberate major-version decision.
94+
- **Library Kotlin `apiVersion` / `languageVersion` pin (2.0).** Consumer compatibility floor; raising it is a deliberate major-version decision. The compiler version itself is not the lever.
9595
- **`minSdk` / JVM target.** Same story.
9696
- **`-Xexplicit-api=strict`.** Removing this would let implicit public declarations ship; keeping it is a consumer-protection invariant.

platforms/android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
plugins {
33
id 'com.android.library' version '9.1.1' apply false
4-
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
5-
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.23' apply false
4+
id 'org.jetbrains.kotlin.android' version '2.3.21' apply false
5+
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21' apply false
66
id 'io.gitlab.arturbosch.detekt' version '1.23.8' apply false
77
id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.18.1'
88
}

platforms/android/gradle.properties

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,32 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
1717
android.useAndroidX=true
1818
# Kotlin code style for this project: "official" or "obsolete":
1919
kotlin.code.style=official
20+
# Do not let the Kotlin Gradle Plugin publish a stdlib dependency that tracks
21+
# the compiler version. The library intentionally compiles with a newer Kotlin
22+
# compiler while keeping its consumer floor at Kotlin 2.0.
23+
kotlin.stdlib.default.dependency=false
2024
# Enables namespacing of each library's R class so that its R class includes only the
2125
# resources declared in the library itself and none from the library's dependencies,
2226
# thereby reducing the size of the R class for that library
2327
android.nonTransitiveRClass=true
2428
android.nonFinalResIds=false
2529
# -----------------------------------------------------------------------------
26-
# Temporary AGP 9 opt-outs that keep the library compilable with Kotlin 1.9.23.
30+
# AGP 9 opt-outs — driven by API-validation tooling, NOT by Kotlin compiler version.
2731
#
28-
# The library is deliberately held at Kotlin 1.9.23 to maximise consumer
29-
# compatibility: consumers on older Kotlin toolchains can still build against
30-
# our AAR without metadata-version warnings or language/API-level breakage.
32+
# AGP 9 ships a built-in Kotlin plugin that replaces `org.jetbrains.kotlin.android`,
33+
# but neither of our API-validation options works with it for Android libraries:
3134
#
32-
# Both opt-outs will be removed in AGP 10 (~mid-2026). Before then, plan to
33-
# migrate to Kotlin 2.x as part of the next major library release, with
34-
# `apiVersion`/`languageVersion` pinned to 1.9 in lib/build.gradle to preserve
35-
# the same consumer contract. Revisit this block at that time.
35+
# - binary-compatibility-validator (BCV) listens for the `kotlin-android` plugin
36+
# id, which AGP's built-in path doesn't apply, so `:lib:apiCheck`/`:lib:apiDump`
37+
# never register. Tracked upstream:
38+
# https://github.com/Kotlin/binary-compatibility-validator/issues/312
39+
# - KGP's experimental `abiValidation` DSL exists for JVM and Multiplatform but
40+
# is not yet available on KotlinAndroidProjectExtension. Umbrella:
41+
# https://youtrack.jetbrains.com/issue/KT-71172
42+
#
43+
# Until either lands (or AGP 10 forces the issue ~mid-2026), we stay on the
44+
# explicit `org.jetbrains.kotlin.android` plugin so BCV keeps gating public API.
3645
# -----------------------------------------------------------------------------
37-
# Keep the explicit org.jetbrains.kotlin.android plugin at 1.9.23.
3846
android.builtInKotlin=false
39-
# KGP 1.9.23 uses the old DSL types (BaseExtension) that AGP 9 removed.
47+
# KGP uses the old DSL types (BaseExtension) that AGP 9 removed.
4048
android.newDsl=false

0 commit comments

Comments
 (0)