Analytics¶
The Starter Template provides a modular, provider-agnostic analytics system built with Clean Architecture. You track events through type-safe AppEvent models while keeping the provider (Mixpanel today) swappable in the data layer.
Setup¶
- Open the constants file:
| composeApp/src/commonMain/.../core/AppConstants.kt | |
|---|---|
- Replace
"add-your-mixpanel-token-here"with your Mixpanel project token.
Info
See the official Mixpanel docs for generating your token.
Architecture¶
| Piece | Role |
|---|---|
AppEvent |
Base analytics event (event name + optional properties) in analytics domain |
AppEvents |
Your sealed hierarchy of all app events (starter ships one in core domain) |
EventsTracker |
Interface that sends events to the provider |
Recommended approach: keep one sealed AppEvents hierarchy (core/shared module) so every screen tracks through the same typed models.
1. Define Events¶
Extend AppEvent with a sealed hierarchy. Starter already provides AppEvents in core domain:
sealed class AppEvents(
event: String,
properties: Map<String, Any>? = null,
) : AppEvent(event, properties) {
constructor(event: String) : this(event = event, properties = null)
constructor(
event: String,
pair: Pair<String, Any>? = null,
) : this(
event = event,
properties = if (pair != null) mapOf(pair) else mapOf(),
)
data object DummyEvent : AppEvents(
event = "dummy_event",
)
data class TrackTrafficSource(
val source: String,
) : AppEvents(
event = "onboarding_traffic_source",
pair = "traffic_source" to source,
)
data class OnPurchaseSuccess(
val productId: String,
) : AppEvents(
event = "purchase_success",
pair = "product_id" to productId,
)
}
Add a new event as another nested type:
data class SignInSuccess(
val userId: String,
) : AppEvents(
event = "sign_in_success",
pair = "user_id" to userId,
)
For multiple properties, pass properties = mapOf(...) instead of pair.
Note
- Prefer
snake_caseevent names. - Keep all events in one sealed class for autocomplete and consistency.
- No need to add methods on
EventsTrackerper event — the type is the event.
2. Track in ViewModel¶
Inject EventsTracker and call track with an AppEvents instance:
| SignInViewModel.kt | |
|---|---|
Another example from onboarding:
eventsTracker.track(
event = AppEvents.TrackTrafficSource(
source = selectedTrafficSource ?: "--",
),
)
Note
- Keep analytics calls in the presentation layer.
- ViewModel is the best place.
You can still call the string overloads (track(event), track(event, pair), track(event, properties)) when needed, but typed AppEvent is preferred.
Replacing Analytics Provider¶
To swap Mixpanel with another provider:
- Implement the
EventsTrackerinterface in the data layer. - Update your Koin module to provide your implementation.
Note
- Domain layer, ViewModels, and Compose code remain unchanged.
- Switching providers does not require rewriting event definitions.