Skip to content

Getting started

This page takes you from an empty Compose screen to a working paywall. Plan on five minutes.

Required
minSdk 26 (Android 8.0)
compileSdk 37
androidx.compose.material3 1.5.0-beta02 or newer (comes with the library)
Kotlin 2.2 or newer (the library is built with 2.2.10)
  1. Add the JitPack repository and the dependency. Pick the tab that matches how your project declares dependencies.

    settings.gradle.kts
    dependencyResolutionManagement {
    repositories {
    google()
    mavenCentral()
    maven("https://jitpack.io")
    }
    }
    app/build.gradle.kts
    dependencies {
    implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")
    }
  2. Sync Gradle. The library brings material3:1.5.0-beta02 with it, so you don’t need to add it yourself.

  3. Add the imports where you build the screen:

    import androidx.compose.runtime.Composable
    import dev.sanikant.m3.paywall.ClassicPlansPaywall
    import dev.sanikant.m3.paywall.PaywallFeature
    import dev.sanikant.m3.paywall.PaywallIcons
    import dev.sanikant.m3.paywall.PaywallLegal
    import dev.sanikant.m3.paywall.PaywallPlan
UpgradeScreen.kt
@Composable
fun UpgradeScreen(onBuy: (planId: String) -> Unit, onRestore: () -> Unit, onDismiss: () -> Unit) {
ClassicPlansPaywall(
plans = listOf(
PaywallPlan(id = "monthly", title = "Monthly", price = "$4.99", period = "per month"),
PaywallPlan(
id = "yearly", title = "Yearly", price = "$29.99", period = "per year",
trialText = "7-day free trial", badge = "Best value",
),
),
features = listOf(
PaywallFeature("Unlimited projects", icon = PaywallIcons.Sparkle),
PaywallFeature("No ads", icon = PaywallIcons.Block),
),
legal = PaywallLegal(termsUrl = "https://example.com/terms", privacyUrl = "https://example.com/privacy"),
onPurchase = { plan -> onBuy(plan.id) },
onRestore = onRestore,
onClose = onDismiss,
)
}

What each part does:

  • plans: what the user can buy. price and period are display strings, so pass the localized price your billing SDK gives you. The plan with a badge is preselected.
  • features: the benefits list. Icons come from PaywallIcons, or pass any ImageVector.
  • legal: your Terms and Privacy URLs. They open in the browser from the footer.
  • onPurchase: gets the selected PaywallPlan. Start your billing flow here.
  • onRestore and onClose: the Restore link and the close button.

Under the button the paywall writes the disclosure line for the selected plan, for example “7-day free trial, then $29.99 per year. Renews automatically until canceled. Cancel anytime in Google Play.”

The paywalls read colors, type and motion from MaterialTheme. For the full Expressive feel, wrap your app in MaterialExpressiveTheme with MotionScheme.expressive():

Theme.kt
@Composable
fun AppTheme(typography: Typography = Typography(), content: @Composable () -> Unit) {
val dark = isSystemInDarkTheme()
val context = LocalContext.current
val colors = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
if (dark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
dark -> darkColorScheme()
else -> expressiveLightColorScheme()
}
MaterialExpressiveTheme(
colorScheme = colors,
motionScheme = MotionScheme.expressive(), // MotionScheme.standard() for calmer springs
typography = typography,
content = content,
)
}

Dynamic color, dark mode and your own font apply automatically. The demo app uses Google Sans Flex through a variable FontFamily:

/** A variable font (e.g. Google Sans Flex in res/font) as a FontFamily, one instance per weight. */
fun variableFontFamily(@FontRes font: Int): FontFamily = FontFamily(
listOf(400, 500, 600, 700, 800, 900).map { weight ->
Font(font, FontWeight(weight), variationSettings = FontVariation.Settings(FontVariation.weight(weight)))
},
)
// Then: AppTheme(typography = Typography(variableFontFamily(R.font.google_sans_flex))) { ... }

Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.