Play Store Release

Prepare signing, Play Console configuration, internal testing, and rollout.

Android distribution means a signed App Bundle, Play Console setup, real testing tracks, and production Supabase / RevenueCat wiring. In-App Updates, In-App Review, and real purchases only behave correctly in Play-delivered builds.

Pre-flight

ItemWhere
Package nameapplicationId = "com.soarstarter.kotlin" in app/build.gradle.kts
VersionversionName = "1.0.0", versionCode = 1
SDK levelsminSdk = 26, targetSdk = 37
SigningRELEASE_* keys in git-ignored local.properties
Legal linksLEGAL_TERMS_URL, LEGAL_PRIVACY_URL, LEGAL_EULA_URL
App LinksLINKING_DOMAIN + hosted .well-known/assetlinks.json
MonetizationGoogle Play products + RevenueCat pro entitlement
BackendProduction Supabase project, migrations, functions, and secrets

Change applicationId before your first Play upload if you are shipping under your own package. After the first upload, the package name is effectively the app identity.

Signing

Generate an upload keystore once:

keytool -genkey -v -keystore soar-upload.jks -keyalg RSA -keysize 2048 \
  -validity 10000 -alias soar-upload

Add the release keys to local.properties:

RELEASE_STORE_FILE=soar-upload.jks
RELEASE_STORE_PASSWORD=your_store_password
RELEASE_KEY_ALIAS=soar-upload
RELEASE_KEY_PASSWORD=your_key_password

The Gradle signing config degrades gracefully. If RELEASE_STORE_FILE is empty or points to a missing file, debug and CI builds still work, while the release artifact remains unsigned. For Play, prefer Play App Signing and use this keystore as the upload key.

LINKING_DOMAIN is used as the Android manifest host placeholder. To make HTTPS App Links verify, host:

https://<your-domain>/.well-known/assetlinks.json

The file must include your package name and the SHA-256 fingerprint of the release signing certificate. Keep soar:// for local custom-scheme links, but use verified App Links for public URLs.

Products and entitlement

The template expects the RevenueCat entitlement id pro and these product ids:

Product idStore type
pro_monthlyGoogle Play monthly auto-renewing subscription
pro_yearlyGoogle Play yearly auto-renewing subscription
pro_lifetimeGoogle Play one-time in-app product

Attach the products to the RevenueCat pro entitlement and the current Android offering. The app renders whichever packages the offering returns.

The app does not use RevenueCat CustomerInfo as the entitlement source of truth. Purchases flow through the revenuecat-webhook Edge Function into public.subscriptions with provider = play_store; EntitlementController reads that Supabase row.

Data safety

Play Console asks what the APK/AAB and enabled services collect. The table below matches the template's shipped integrations. If you ship without a key or remove a feature, adjust the declaration.

Data typeCollectedPurposeSource
Email addressYesAccount management, authSupabase Auth
User IDsYesAccount management, analytics, entitlement lookupSupabase / PostHog / RevenueCat
Name and profile infoIf user enters itApp functionalityprofiles table
Photos or filesIf user uploadsApp functionalitySupabase Storage sample
Todos / user contentYes, if usedApp functionalitytodos table
App interactionsIf PostHog key is setAnalyticsPostHog
Crash logs and diagnosticsIf Sentry DSN is setApp stabilitySentry
Device or advertising IDsIf analytics/FCM are shippedAnalytics, push deliveryPostHog / Firebase / AD_ID permission

Declare encrypted in transit for networked data. Provide a privacy policy URL; it should match LEGAL_PRIVACY_URL.

The in-app delete-account action is currently client-side cleanup: it deletes the user's todos, nulls profile fields, and signs out locally. It does not delete the Supabase Auth user. Google Play's account-deletion policy expects a real deletion path, so port or add a server-side delete-account Edge Function before release.

Build and upload

Run the quality gate first:

./gradlew testDebugUnitTest lintDebug

Build the App Bundle:

./gradlew bundleRelease

The output is:

app/build/outputs/bundle/release/app-release.aab

Upload the .aab to Internal testing first. Add testers, install from the Play opt-in link, then test the Play-only surfaces from that installed build:

SurfaceWhat to verify
PurchasesLicense tester can buy, webhook writes subscriptions, Pro guides unlock
FCMgoogle-services.json is present, send-test-push function sends a notification
In-App UpdatesMIN_APP_VERSION / LATEST_APP_VERSION strategy does not nag the current version
In-App ReviewTrigger logic can request Play review without crashing
App LinksHTTPS links open the app after domain verification

Promote Internal to Closed, Open, or Production when the release build has been smoke-tested.

Review notes

Mention these items in Play review notes:

TopicNote
In-app purchaseMonthly, yearly, and optional lifetime products unlock Pro developer guides under Showcase
Test pathReviewer can open Home or Me -> Subscription to reach the paywall
Promo codesRedeem code opens Google Play redemption
Account-gated screensReviewers can create an email/password account
Delete accountProvide the in-app path and the external account-deletion URL

Release checklist

  • versionCode is higher than the last Play upload.
  • Release keystore exists and bundleRelease creates a signed .aab.
  • SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY point to the production project.
  • Missing todos / profiles schema has been applied if the template repo has not yet added those migrations.
  • revenuecat-webhook is deployed with REVENUECAT_WEBHOOK_AUTH.
  • send-test-push is deployed with Firebase Admin secrets if reviewers should test push.
  • REVENUECAT_ANDROID_KEY is set and Play products are active.
  • APP_UPDATE_URL points to the Play listing once it exists.
  • Legal URLs resolve over HTTPS.
  • App icon, feature graphic, phone screenshots, and tablet screenshots are uploaded.
  • Data safety, privacy policy, app access, ads, content rating, and target audience forms are complete.

On this page