Example Features

Todos, profile, upload, refer, permissions, and other replaceable product surfaces.

These screens are working product examples, not framework code. Keep the app patterns they demonstrate, then replace the copy, fields, and business logic with your own app's features.

What it provides

SurfaceStatusImplementation
HomeExamplefeature/home/HomeScreen.kt + HomeViewModel
TodosExamplefeature/todos/ + TodosRepository
ProfileExamplefeature/profile/ + ProfileRepository
ReferExamplefeature/refer/ReferScreen.kt
PermissionsExamplefeature/permissions/PermissionsScreen.kt
Upload sampleExamplefeature/upload/ + UploadsRepository

Home

The Home tab is the product landing surface inside the app. It demonstrates a stack overview, entitlement-aware license card, capability preview, and shortcuts into live demos.

HomeViewModel reads two sources:

SourcePurpose
PurchasesController.isConfiguredKnows whether REVENUECAT_ANDROID_KEY is present
EntitlementController.stateKnows whether the signed-in account has the Supabase-backed pro entitlement

That maps into HomeLicenseState.Owned, Available, or Unconfigured. The screen does not reach into navigation directly; HomeRoute receives callbacks such as onOpenTodos, onOpenUploads, onGetTemplate, onManageLicense, and onRedeemCode. Copy that callback-driven pattern when adding your own launch surface.

The "What's inside" preview links to the removable Showcase leaf. Its seams are tagged with // Showcase promo (removable leaf), so it can be removed without touching the core Home demos.

Todos

Todos is the reference CRUD feature. Its canonical entry is the Home stack route AppRoutes.Todos; it is not one of the bottom tabs.

CapabilityCode path
Sign-in gateTodosScreen shows a sign-in card when SessionController.currentUser is empty
Load/create/update/deleteTodosViewModel -> TodosRepository -> SupabaseTodosRemoteDataSource
FiltersTodosFilter.All, Active, Completed rendered as FilterChips
Inline editRow-local edit mode with save/cancel buttons
ReorderingUp/down buttons, optimistic UI, then setSortOrders()
Error stateerrorMessage in TodosUiState

TodosRepository.clearCompleted() exists, but the current Compose screen does not expose a clear-completed confirmation dialog. If you want that action, wire it through TodosViewModel and add a Material AlertDialog before calling the repository helper.

Profile

The Me tab is the account hub. Signed-out users see a centered sign-in prompt; signed-in users see profile, license, account, notification, permissions, legal, and sign-out rows.

PersonalInfoScreen demonstrates editable profile fields and app preferences:

Field or settingSource
Full nameprofiles.full_name
Bioprofiles.bio
Phoneprofiles.phone
Locationprofiles.location
Avatar displayprofiles.avatar_url via AppAsyncImage
ThemeThemeController + DataStore
LanguageLocaleController + DataStore

AccountSecurityScreen demonstrates the account actions you are likely to keep:

ActionImplementation
Change emailSends an email-change OTP, then verifies it
Change passwordValidates the new password and calls Supabase Auth
Biometric app lockPrompts with BiometricPrompt, then persists the toggle
Sign out other sessionsauth.signOut(SignOutScope.OTHERS)
Delete accountConfirmation dialog, then AuthRepository.deleteAccount()

Delete account is intentionally documented as implemented: it deletes the user's todos, nulls profile fields, and signs out locally. It does not delete the Supabase auth.users row because no delete-account Edge Function ships yet. Add a server-side deletion function before relying on this for Google Play account-deletion policy compliance.

Refer

ReferScreen builds a Play Store URL from the app package name:

https://play.google.com/store/apps/details?id=<package-name>

It launches Android's system share sheet with Intent.ACTION_SEND, Intent.EXTRA_TITLE, and Intent.EXTRA_TEXT. A successful share records a rating trigger through RatingController.recordTrigger(activity, "referral_shared"), which feeds the in-app review cooldown logic.

Permissions

The Permissions screen is a priming screen: it explains why the app asks before launching a runtime permission request.

RowAndroid permissionNotes
NotificationsPOST_NOTIFICATIONS on Android 13+; pre-granted below API 33Needed before FCM notifications can appear
CameraCAMERADemonstrates a normal dangerous permission
Trackingcom.google.android.gms.permission.AD_IDAdvertising ID permission used by analytics/attribution SDKs

The README still mentions Photos, but the current code does not request a photos/media runtime permission. The upload sample uses Android's system photo picker, so it can select media without broad photo-library access.

Upload sample

The upload sample is a practical Supabase Storage example:

PickMultipleVisualMedia -> UploadViewModel -> UploadsRepository -> sample-uploads

UploadViewModel resolves display name, MIME type, and size from the selected URI, rejects files larger than 25 MB, reads bytes off the main thread, and collects uploadAsFlow() progress into per-job rows. Successful uploads store objects at:

<user-id>/<timestamp>-<sanitized-file-name>

The bucket id is hardcoded as UploadsRepository.BUCKET_ID = "sample-uploads". Replace the bucket, policy, and object path when converting this into your own media feature.

Keep the patterns

  1. Put the route composable at the feature boundary and pass callbacks inward.
  2. Keep screen composables stateless where possible.
  3. Let ViewModels translate repository AppResult values into one UI state.
  4. Route all Supabase table access through a repository or remote data source.
  5. Use the shared component set before creating one-off controls.
  6. Label examples honestly in your product so starter/demo code does not ship as accidental product copy.

On this page