From ce25020d1abaf13711f8f53e60b072fa1eb5039b Mon Sep 17 00:00:00 2001 From: JayShortway <29483617+JayShortway@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:31:50 +0100 Subject: [PATCH 1/3] Adds purchases-core --- .circleci/config.yml | 51 +++++++++++++++++++ purchases/build.gradle.kts | 1 + .../com/revenuecat/purchases/Purchases.kt | 5 ++ settings.gradle.kts | 10 ++++ test-apps/sdksizetesting/settings.gradle.kts | 15 ++++++ 5 files changed, 82 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 31c813851a..9e84469d06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -932,6 +932,9 @@ workflows: build_type: "debug" requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - test: name: test_defaults_bc7_release flavor: "defaults" @@ -939,6 +942,9 @@ workflows: build_type: "release" requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - test: name: test_cec_<< matrix.build_type >> flavor: "customEntitlementComputation" @@ -948,6 +954,9 @@ workflows: matrix: parameters: build_type: ["debug", "release"] + # FIXME We only need read access here. + context: + - github-packages-publishing # Special case for defaults BC8 release with kover - test: name: test_defaults_bc8_release @@ -957,45 +966,87 @@ workflows: run_kover: true requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - verify-compatibility: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - lint: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - detekt: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - metalava: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - assemble-purchase-tester: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - assemble-paywall-tester-release: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - test_dokka_hide_internal: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - run-backend-integration-tests: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - record-and-upload-paparazzi-revenuecatui-snapshots: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - run-revenuecatui-ui-tests: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - emerge_purchases_ui_snapshot_tests: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - emerge_size_analysis_tests: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - run-maestro-e2e-tests: requires: - prepare-tests + # FIXME We only need read access here. + context: + - github-packages-publishing - integration-tests-build: *release-branches - run-firebase-tests-purchases-custom-entitlement-computation-integration-test: <<: *release-branches diff --git a/purchases/build.gradle.kts b/purchases/build.gradle.kts index 1f5004c2e0..ef6f075b0f 100644 --- a/purchases/build.gradle.kts +++ b/purchases/build.gradle.kts @@ -169,6 +169,7 @@ dependencies { implementation(libs.tink) implementation(libs.playServices.ads.identifier) implementation(libs.coroutines.core) + implementation("com.revenuecat.purchases:purchases-core:0.0.0-rust-SNAPSHOT") "bc8Api"(libs.billing.bc8) "bc7Api"(libs.billing.bc7) diff --git a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt index 8871bbb445..bffc96ad95 100644 --- a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt +++ b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt @@ -15,6 +15,7 @@ import com.revenuecat.purchases.common.errorLog import com.revenuecat.purchases.common.events.FeatureEvent import com.revenuecat.purchases.common.infoLog import com.revenuecat.purchases.common.log +import uniffi.purchases_core.add import com.revenuecat.purchases.customercenter.CustomerCenterListener import com.revenuecat.purchases.deeplinks.DeepLinkParser import com.revenuecat.purchases.interfaces.Callback @@ -1172,6 +1173,10 @@ class Purchases internal constructor( fun configure( configuration: PurchasesConfiguration, ): Purchases { + // Call Rust add() function to verify integration + val rustResult = add(2uL, 3uL) + infoLog { "Rust add(2, 3) = $rustResult" } + if (isConfigured) { if (backingFieldSharedInstance?.purchasesOrchestrator?.currentConfiguration == configuration) { infoLog { ConfigureStrings.INSTANCE_ALREADY_EXISTS_WITH_SAME_CONFIG } diff --git a/settings.gradle.kts b/settings.gradle.kts index 062c2a73af..ff302b5f2f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -49,6 +49,16 @@ dependencyResolutionManagement { // fallback for the rest of the dependencies mavenCentral() + + // GitHub Packages for purchases-core Rust library + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/RevenueCat/purchases-core") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: "" + } + } } } diff --git a/test-apps/sdksizetesting/settings.gradle.kts b/test-apps/sdksizetesting/settings.gradle.kts index c7b145085d..dc056c7fce 100644 --- a/test-apps/sdksizetesting/settings.gradle.kts +++ b/test-apps/sdksizetesting/settings.gradle.kts @@ -21,6 +21,21 @@ dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenLocal() + // GitHub Packages for purchases-core Rust library (transitive dependency) + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/RevenueCat/purchases-core") + credentials { + username = System.getenv("GITHUB_ACTOR") ?: "" + password = System.getenv("GITHUB_TOKEN") ?: "" + } + content { + // Only fetch purchases-core from GitHub Packages + includeModule("com.revenuecat.purchases", "purchases-core") + includeModule("com.revenuecat.purchases", "purchases-core-android") + includeModule("com.revenuecat.purchases", "purchases-core-jvm") + } + } google { content { // Exclude to make sure we use dependency from mavenLocal From 20dd752b8de8dfe2700b48b1006752b33743ee24 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Tue, 31 Mar 2026 16:28:42 +0200 Subject: [PATCH 2/3] Add usages of suspend function to test --- .../com/revenuecat/purchases/Purchases.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt index 94762cf53e..434cd1c8dd 100644 --- a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt +++ b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt @@ -16,6 +16,7 @@ import com.revenuecat.purchases.common.events.FeatureEvent import com.revenuecat.purchases.common.infoLog import com.revenuecat.purchases.common.log import uniffi.purchases_core.add +import uniffi.purchases_core.performOperation import com.revenuecat.purchases.customercenter.CustomerCenterListener import com.revenuecat.purchases.deeplinks.DeepLinkParser import com.revenuecat.purchases.interfaces.Callback @@ -44,6 +45,9 @@ import com.revenuecat.purchases.strings.BillingStrings import com.revenuecat.purchases.strings.ConfigureStrings import com.revenuecat.purchases.utils.DefaultIsDebugBuildProvider import com.revenuecat.purchases.virtualcurrencies.VirtualCurrencies +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import uniffi.purchases_core.OperationMode import java.net.URL import java.util.Locale @@ -1287,6 +1291,20 @@ public class Purchases internal constructor( // Call Rust add() function to verify integration val rustResult = add(2uL, 3uL) infoLog { "Rust add(2, 3) = $rustResult" } + GlobalScope.launch { + val successResult = performOperation(OperationMode.SUCCESS) + errorLog { "Operation result in Rust: $successResult" } + try { + val failureResult = performOperation(OperationMode.ERROR) + } catch (e: Exception) { + errorLog(e) { "Error performing operation in Rust" } + } + try { + val timeoutResult = performOperation(OperationMode.TIMEOUT) + } catch (e: Exception) { + errorLog(e) { "Timeout performing operation in Rust" } + } + } if (isConfigured) { if (backingFieldSharedInstance?.purchasesOrchestrator?.currentConfiguration == configuration) { From 6e3521182f118a3c296d0a8a5e5671ce115d58b7 Mon Sep 17 00:00:00 2001 From: Toni Rico Date: Thu, 2 Apr 2026 15:41:10 +0200 Subject: [PATCH 3/3] Add example of usage of 2-way API --- .../revenuecat/purchases/NativeHttpClient.kt | 25 +++++++++++++++++++ .../com/revenuecat/purchases/Purchases.kt | 10 ++++++++ 2 files changed, 35 insertions(+) create mode 100644 purchases/src/defaults/kotlin/com/revenuecat/purchases/NativeHttpClient.kt diff --git a/purchases/src/defaults/kotlin/com/revenuecat/purchases/NativeHttpClient.kt b/purchases/src/defaults/kotlin/com/revenuecat/purchases/NativeHttpClient.kt new file mode 100644 index 0000000000..980822fcce --- /dev/null +++ b/purchases/src/defaults/kotlin/com/revenuecat/purchases/NativeHttpClient.kt @@ -0,0 +1,25 @@ +package com.revenuecat.purchases + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import uniffi.purchases_core.HttpClient +import uniffi.purchases_core.HttpException +import java.net.URL + +/** + * PoC: Kotlin implementation of the Rust `HttpClient` foreign trait. + * Rust calls `fetch(url)` on this object and awaits the result. + */ +internal class NativeHttpClient : HttpClient { + + override suspend fun fetch(url: String): String { + return withContext(Dispatchers.IO) { + try { + URL(url).readText() + } catch (e: Exception) { + throw HttpException.RequestFailed("${e.javaClass.simpleName}: ${e.message}") + } + } + } + +} diff --git a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt index 434cd1c8dd..4bb0fe926c 100644 --- a/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt +++ b/purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt @@ -16,6 +16,7 @@ import com.revenuecat.purchases.common.events.FeatureEvent import com.revenuecat.purchases.common.infoLog import com.revenuecat.purchases.common.log import uniffi.purchases_core.add +import uniffi.purchases_core.fetchWithNative import uniffi.purchases_core.performOperation import com.revenuecat.purchases.customercenter.CustomerCenterListener import com.revenuecat.purchases.deeplinks.DeepLinkParser @@ -47,6 +48,7 @@ import com.revenuecat.purchases.utils.DefaultIsDebugBuildProvider import com.revenuecat.purchases.virtualcurrencies.VirtualCurrencies import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import uniffi.purchases_core.HttpException import uniffi.purchases_core.OperationMode import java.net.URL import java.util.Locale @@ -1304,6 +1306,14 @@ public class Purchases internal constructor( } catch (e: Exception) { errorLog(e) { "Timeout performing operation in Rust" } } + + // PoC: 2-way communication — Rust calls back into Kotlin via HttpClient + try { + val result = fetchWithNative(NativeHttpClient(), "https://httpbin.org/get") + infoLog { "[Android] Rust round-trip result: $result" } + } catch (e: Exception) { + errorLog(e) { "[Android] Rust round-trip error" } + } } if (isConfigured) {