Connect a Compose paywall to RevenueCat
Short answer: fetch the current Offering, map each Package to a plan (package identifier
as the id, product.price.formatted as the price), then call purchaseWith with the matching
package from onPurchase, and restorePurchasesWith from onRestore.
Add the dependency
Section titled “Add the dependency”implementation("com.revenuecat.purchases:purchases:10.29.0")-
Configure the SDK once, for example in
Application.onCreate:// Read the public SDK key from local.properties through BuildConfig. Never commit it.fun configureRevenueCat(context: Context, apiKey: String) {Purchases.configure(PurchasesConfiguration.Builder(context, apiKey).build())} -
Map packages to plans. The package type gives a readable title, and the product’s default option tells you about a free trial:
fun Package.toPaywallPlan(): PaywallPlan = PaywallPlan(id = identifier,title = when (packageType) {PackageType.ANNUAL -> "Yearly"PackageType.MONTHLY -> "Monthly"PackageType.LIFETIME -> "Lifetime"else -> product.title},price = product.price.formatted,period = product.period?.let { "per ${it.unit.name.lowercase()}" } ?: "one-time",trialText = product.defaultOption?.freePhase?.billingPeriod?.let { "${it.value}-${it.unit.word()} free trial" },badge = if (packageType == PackageType.ANNUAL) "Best value" else null,isSubscription = product.period != null,)private fun Period.Unit.word(): String = name.lowercase() -
Load the current Offering. Keep the packages, because you need them to buy:
fun loadPlans(onLoaded: (packages: List<Package>, plans: List<PaywallPlan>) -> Unit) {Purchases.sharedInstance.getOfferingsWith(onError = { error -> println("Offerings failed: ${error.message}") },onSuccess = { offerings ->val packages = offerings.current?.availablePackages.orEmpty()onLoaded(packages, packages.map { it.toPaywallPlan() })},)} -
Purchase and restore, then check your entitlement (here called
pro):fun purchase(activity: Activity, packages: List<Package>, plan: PaywallPlan, onUnlocked: () -> Unit) {val pkg = packages.first { it.identifier == plan.id }Purchases.sharedInstance.purchaseWith(PurchaseParams.Builder(activity, pkg).build(),onError = { error, userCancelled -> if (!userCancelled) println(error.message) },onSuccess = { _, customerInfo ->if (customerInfo.entitlements["pro"]?.isActive == true) onUnlocked()},)}fun restore(onUnlocked: () -> Unit) {Purchases.sharedInstance.restorePurchasesWith(onError = { error -> println(error.message) },onSuccess = { customerInfo ->if (customerInfo.entitlements["pro"]?.isActive == true) onUnlocked()},)} -
Show any of the paywalls with the plans, passing
purchaseandrestoreas callbacks. See all designs.
RevenueCat Paywalls or your own UI?
Section titled “RevenueCat Paywalls or your own UI?”RevenueCat ships its own Compose paywall module, purchases-ui, which renders paywalls designed
in the RevenueCat dashboard. The two approaches solve different problems:
| RevenueCat Paywalls | M3 Expressive Paywall + RevenueCat | |
|---|---|---|
| Where the design lives | RevenueCat dashboard (remote) | Your code |
| Change without an app update | Yes | No |
| Material 3 Expressive components | Not the focus | Yes |
| Works without RevenueCat | No | Yes, with any billing source |
More detail on the comparison page.
Related
Section titled “Related”FAQ
Isn't RevenueCat's own paywall enough?
RevenueCat Paywalls (the purchases-ui module) are configured remotely in the RevenueCat dashboard. That is great for changing paywalls without app updates. A code-based paywall like this library suits teams that want the UI in their repository, Material 3 Expressive design, or no dependency on a remote paywall config.
Where does the RevenueCat API key go?
Use the public SDK key from the RevenueCat dashboard, keep it in local.properties and expose it through BuildConfig. Don't commit it to a public repository.
Which RevenueCat SDK version is used here?
com.revenuecat.purchases:purchases 10.29.0. The snippets are compiled against it.
Try it in your app
Add the library (JitPack, minSdk 26), then follow Getting started.
implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")⭐ If this guide helped, star M3 Expressive Paywall on GitHub. It helps other Compose developers find it.
Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.