Observability

PostHog analytics, Sentry crash reporting, test controls, and consent behavior.

The Kotlin template has two optional observability layers: PostHog for product analytics and Sentry for crash reporting. Both are configured from local.properties, and both no-op when their keys are absent.

What it provides

SurfaceStatusImplementation
Product analyticsIntegratedAnalyticsController wraps PostHog Android
Typed analytics eventsIntegratedAnalyticsEvent sealed class
User identityIntegratedPostHog identify on sign-in, reset on sign-out
Crash reportingIntegratedSentryReporter initializes Sentry Android
User taggingIntegratedSentry scope user follows SessionController.currentUser
Error fallbackIntegratedAppErrorBus + ErrorBoundary + in-app feedback

Key files

Configuration

# Sentry
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0

# PostHog
POSTHOG_API_KEY=phc_your_key
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_DISABLED=false
POSTHOG_DEBUG=false

Missing SENTRY_DSN skips Sentry SDK initialization and capture calls return without sending. Missing POSTHOG_API_KEY, or POSTHOG_DISABLED=true, makes analytics track() and screen() no-op.

PostHog analytics

SoarApp.onCreate() calls analyticsController.start(). When configured, it builds a PostHogAndroidConfig with:

OptionValue
API keyBuildConfig.POSTHOG_API_KEY
HostBuildConfig.POSTHOG_HOST, default https://us.i.posthog.com
Lifecycle capturecaptureApplicationLifecycleEvents = true
Debug loggingBuildConfig.POSTHOG_DEBUG

The controller subscribes to SessionController.currentUser. On sign-in it identifies the user by Supabase UUID and sends safe account properties such as email, email domain, account creation time, and environment. On sign-out it calls PostHog.reset().

Typed app events live in AnalyticsEvent:

EventNameCurrent call sites
ScreenViewedscreen viewedAvailable helper event
OnboardingCompletedonboarding completedOnboardingGate
PaywallViewedpaywall viewedAvailable event type
ReferralInviteSharedreferral invite sharedAvailable event type
PurchaseCompletedpurchase completedPaywallViewModel after purchase
PurchaseRestoredpurchase restoredAvailable event type

Add new events by extending the sealed class and keeping property names stable.

Sentry crash reporting

SoarApp.onCreate() also calls sentryReporter.start(). When SENTRY_DSN is present, the reporter initializes Sentry Android with:

OptionValue
Environmentdevelopment for debug builds, production otherwise
Release<applicationId>@<versionName>+<versionCode>
Trace sample rate1.0 in debug, 0.2 in non-debug
Stack tracesAttached

The reporter follows SessionController.currentUser and tags Sentry scope with the Supabase user id and email while signed in.

Error boundary

MainActivity wraps the app navigation in:

ErrorBoundary(errorBus = appErrorBus, reporter = sentryReporter)

View models and controllers can call AppErrorBus.report(throwable, boundary). The bus captures the exception in Sentry when configured and stores the active error in a StateFlow. ErrorBoundary then renders the in-app fallback with:

  • Try again, which clears the active error.
  • Send a report, which captures a Sentry feedback event.
  • A debug-only detail panel with the message and stack trace.

This boundary handles errors routed through AppErrorBus. It does not replace Sentry's normal uncaught-exception capture for process crashes.

Privacy and Play data safety

The current code starts PostHog from SoarApp.onCreate(), before the legal ConsentGate is accepted. If your app must wait for consent before tracking, move analyticsController.start() behind ConsentController.hasAcceptedCurrent or ship with POSTHOG_DISABLED=true until the user has accepted.

Play Console data safety declarations should reflect the SDKs you actually ship. With the template defaults, expect to account for:

Data categoryTypical source
User IDs and emailSupabase Auth, PostHog identify, Sentry user scope
App interactionsPostHog events and lifecycle capture
Crash logs and diagnosticsSentry
Device identifiersAdvertising ID permission, Firebase, PostHog

If you remove PostHog or Firebase, revisit AndroidManifest.xml, SDK dependencies, and the Play data-safety form together.

Earlier planning notes mention a dev-only feature/test/ screen. That screen is not present in the current Kotlin codebase, so manual observability tests should be done from the real call sites or by adding your own debug-only trigger screen.

Verify

  • Leave all observability keys empty and confirm the app starts without crashes or network setup errors.
  • Add POSTHOG_API_KEY, sign in, and confirm PostHog identifies the Supabase user id.
  • Complete onboarding and confirm onboarding completed is captured.
  • Complete a test purchase and confirm purchase completed is captured.
  • Add SENTRY_DSN, trigger a routed AppErrorBus.report(), and confirm the fallback screen plus Sentry event.
  • Sign out and confirm PostHog resets and Sentry clears the user scope.

On this page