Integrate RevenueCat billing SDK with KMP architecture#106
Draft
Integrate RevenueCat billing SDK with KMP architecture#106
Conversation
…layers Co-authored-by: ricky9667 <55730003+ricky9667@users.noreply.github.com>
Co-authored-by: ricky9667 <55730003+ricky9667@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Integrate RevenueCat billing into HushTimer
Integrate RevenueCat billing SDK with KMP architecture
Feb 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integrate RevenueCat's
purchases-kmpSDK (v2.4.0+17.30.0) as a feature package in:composeApp.billing/feature with domain/data/presentation layers, platform-specific initializationContext / Links
Implementation details
Architecture: Feature package in
:composeApp(not separate module), matchingtimer/,session/,settings/structure.Domain layer (
billing/domain/):BillingServiceinterface:observeBillingState(),refreshCustomerInfo(),purchase(),restorePurchases()BillingStatesealed interface:Loading | Ready(isPro, activeEntitlements) | Unavailable | ErrorEntitlementInfodata classData layer (
billing/data/):RevenueCatBillingService(commonMain,internal): wrapsPurchases.sharedInstance, mapsCustomerInfoto state, tracks "pro" entitlementBillingInitializer(expect/actual): platform-specific SDK configPurchases.configure(REVENUECAT_GOOGLE_API_KEY)Purchases.configure(REVENUECAT_APPLE_API_KEY)NoOpBillingService(jvmMain): returnsUnavailablestate (RevenueCat doesn't support Desktop)Presentation layer:
BillingViewModel: exposesStateFlow<BillingState>, delegates to serviceDI wiring:
Initialization: added
initializeBilling()calls toHushTimerApplication.onCreate()(Android) andMainViewControllerconfigure block (iOS).Security: API keys via BuildKonfig with empty string fallbacks—no hardcoded secrets. All implementations
internal, only interfaces/ViewModel public.Trade-offs:
PurchasesHybridCommonv17.30.0 (KMP toolchain limitation)Screenshots / API changes (if applicable)
N/A (infrastructure only, no UI changes)
Testing
Requires API keys in
local.propertiesfor runtime testing:BillingService)Note: Repository has pre-existing AGP 9.0.0 build issue preventing
./gradlewexecution. Unrelated to this PR.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
dl.google.com/usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -Xmx4G -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
Overview
Integrate RevenueCat billing into HushTimer using the official purchases-kmp SDK as a feature package inside the existing
:composeAppKMP module. This follows the samedata//domain//presentation/structure used by all other features (timer/,session/,settings/,config/).Reference documentation: https://www.revenuecat.com/docs/getting-started/installation/kotlin-multiplatform
Architecture Decisions
:composeAppalongside other features, consistent with the project's established pattern.purchases-kmpSDK supports Android and iOS fromcommonMain. Desktop/JVM gets a no-op stub.BillingServiceinterface allows swapping the implementation later.internalvisibility on implementation details for a soft boundary.Implementation Plan
Phase 1: Gradle & Dependency Setup
1.1 —
gradle/libs.versions.tomlAdd entries in alphabetical order (per AGENTS.md):
1.2 —
composeApp/build.gradle.ktsAdd to
commonMain.dependencies:1.3 — API key management via BuildKonfig
In the
buildkonfig { defaultConfigs { } }block incomposeApp/build.gradle.kts, add:Phase 2: Domain Layer —
billing/domain/Create these files under
composeApp/src/commonMain/kotlin/com/rickyhu/hushtimer/billing/domain/:2.1 —
EntitlementInfo.kt2.2 —
BillingState.kt2.3 —
BillingService.ktPhase 3: Data Layer —
billing/data/3.1 —
RevenueCatBillingService.ktincommonMainAn
internalclass implementingBillingServicethat wraps RevenueCat'sPurchasesKMP API:Purchases.sharedInstanceto get customer info, offerings, make purchases, and restoreCustomerInfotoBillingState.ReadyMutableStateFlowPRO_ENTITLEMENT_ID = "pro"(matching RevenueCat dashboard)3.2 —
BillingInitializer.kt— expect/actual for platform-specific SDK configuration:commonMain:expect fun initializeBilling()androidMain: callsPurchases.configure(apiKey = BuildKonfig.REVENUECAT_GOOGLE_API_KEY)— on Android the SDK gets context automatically via its ContentProvideriosMain: callsPurchases.configure(apiKey = BuildKonfig.REVENUECAT_APPLE_API_KEY)jvmMain: no-op (RevenueCat doesn't support Desktop)Phase 4: Desktop No-Op
4.1 —
NoOpBillingService.ktinjvmMainPhase 5: Presentation Layer
5.1 —
BillingViewModel.ktincommonMain