Overview
A full-featured Jetpack Compose Android starter with Supabase, RevenueCat, push notifications, and analytics wired in.
The Kotlin (Android) template is a full-featured Jetpack Compose starter for phones and tablets. It ships with a Supabase backend, RevenueCat subscriptions, account-bound Pro entitlements, Firebase Cloud Messaging, deep links, analytics, crash reporting, and the Android launch gates already wired together.
The Android plumbing above is real and integrated. Some product surfaces are intentionally Example or Removable demo features for you to replace before launch.
Minimum SDK: 26. Target SDK: 37. Application id:
com.soarstarter.kotlin.
Graceful degradation
The template reads local Android configuration from local.properties into
BuildConfig. Missing string keys become empty strings, and the owning
controllers check those values before starting service-specific work. That lets
you build and boot the app first, then wire Supabase, RevenueCat, FCM, PostHog,
Sentry, and release signing one at a time. See
Configuration.
Tech stack
| Layer | Choice |
|---|---|
| UI | Jetpack Compose + Material 3 |
| Architecture | MVVM (ViewModel + StateFlow) + Repository pattern |
| Dependency injection | Hilt with KSP |
| Navigation | Navigation Compose with type-safe @Serializable routes |
| Backend | supabase-kt (Auth, PostgREST, Storage, Edge Functions) |
| Network | Ktor OkHttp engine + Coroutines / Flow |
| Persistence | DataStore Preferences for app state; supabase-kt SettingsSessionManager for restored auth sessions |
| Image loading | Coil 3 |
| Push | Firebase Cloud Messaging |
| Subscriptions | RevenueCat purchases + Supabase entitlement read model |
| Analytics | PostHog Android SDK |
| Crash reporting | Sentry Android SDK |
| Biometric | androidx.biometric / BiometricPrompt |
| Play services | Play In-App Updates + Play In-App Review |
| Deep links | App Links + custom soar:// scheme |
| i18n | values/strings.xml + values-zh/strings.xml with runtime locale switching |
Feature status
Features are labelled consistently throughout these docs:
- Integrated — fully wired; activates when its key or service is configured.
- Example — a working demo feature meant to be replaced with your own.
- Removable demo — the Showcase tab and gated guides; the code is fenced for deletion.
- Play-only — needs Google Play services and/or a Play-delivered build.
| Feature | Status | Notes |
|---|---|---|
| Authentication | Integrated | Email/password, sign-up with email OTP, forgot/reset password, change email/password, sign out other sessions |
| Google Sign-In | Integrated | Android Credential Manager ID token -> Supabase; hidden when GOOGLE_WEB_CLIENT_ID is empty |
| Profile | Example | Avatar, personal info, account security, theme/language controls |
| Todos | Example | Full CRUD reference feature; pushed from the Home stack, not a tab |
| Upload sample | Example | Photo picker -> Supabase Storage bucket sample-uploads |
| Subscriptions | Integrated | RevenueCat purchase UI; entitlement source of truth is the Supabase subscriptions read model |
| Push notifications | Integrated | FCM token registration, in-app inbox, and signed-in test-push function |
| Deep links | Integrated | soar:// plus HTTPS App Links via LINKING_DOMAIN |
| Analytics | Integrated | PostHog identify/reset and typed events |
| Crash reporting | Integrated | Sentry setup, ErrorBoundary, user tagging, feedback events |
| App gates | Integrated | Biometric lock, update gate, onboarding, legal consent, auth routing |
| Permissions priming | Integrated | Notifications, camera, and advertising-ID tracking education screens |
| Store review | Play-only | Google Play in-app review smart trigger |
| In-App Updates | Play-only | Force/soft update prompts; falls back to Play URL |
| i18n | Integrated | English + Simplified Chinese; runtime switch through LocaleController and LocalizedAppHost |
| Theming | Integrated | Light, dark, system, and dynamic color settings persisted in DataStore |
| Offline banner | Integrated | Connectivity-backed OfflineBanner component |
| Toast / haptics | Integrated | Top toast host with success/error haptic feedback |
| Refer a friend | Example | Android share sheet via ACTION_SEND |
| Component gallery | Example | Local Compose component catalog and detail demos |
| Showcase + guides | Removable demo | Four-tab shell includes Showcase; developer guides are purchase-gated inside it |
App surface map
The app is composed outside-in:
- Activity host —
MainActivityapplies theme and locale, then wraps the app inBiometricGate -> ErrorBoundary -> AppNavGraph, withToastHostmounted over the navigation content. - Gate stack —
AppNavGraphevaluatesUpdateGate -> OnboardingGate -> ConsentGate, then mounts the main navigation graph. Auth routes are pushed when a signed-in-only action needs them, and popped whenSessionController.currentUserbecomes non-null. - Main shell —
MainShellRouteowns four tabs (Home, Component, Showcase, Me) plus a navigation drawer. The Todos screen lives under the Home graph (AppRoutes.Todos), so it is a canonical example feature rather than a fifth tab.
Cross-platform entitlement
The Android app treats public.subscriptions as the account entitlement source
of truth. RevenueCat handles Google Play purchase and restore flows, then the
revenuecat-webhook Edge Function writes provider = play_store rows into
Supabase. EntitlementController reads that table through
SubscriptionRepository; it does not treat RevenueCat CustomerInfo as the
unlock source for account-gated surfaces. The RevenueCat entitlement id is the
hardcoded constant pro.
Start here
Installation
Open the project, sync Gradle, and boot the app.
Configuration
local.properties keys and service behavior.
Supabase Setup
Auth, database tables, storage, and Edge Functions.
Authentication
Email auth, OTP callbacks, and Google Sign-In.
Subscriptions
RevenueCat purchases with Supabase entitlement state.
Play Store Release
Signing, Play Console setup, testing, and rollout.
