Skip to content

Quick start: Android

On this page you send your first event and see it in your app's log. You don't need an account with any analytics service yet: you start with Herald's log module, which prints each event instead of sending it. At the end you add Firebase.

Add the core library and the log module. The Android SDK page lists all the modules.

dependencies {
    implementation("io.github.mkhytarmkhoian:herald-core:1.4.0")
    implementation("io.github.mkhytarmkhoian:herald-log:1.4.0")
}
dependencies {
    implementation 'io.github.mkhytarmkhoian:herald-core:1.4.0'
    implementation 'io.github.mkhytarmkhoian:herald-log:1.4.0'
}

1. Describe what happened

An event is a small class in your app. It has a name, and parameters that describe what happened:

data class CheckoutStarted(val plan: String, val seats: Int) : Event {
    override val name = "checkout_started"
    override val parameters = parameters {
        put("plan", plan)
        put("seats", seats)
    }
}

Parameters keep their type. seats reaches every service as the number 3, not as the text "3", so you can still sum and filter by it there.

2. Create Herald

Herald is the object your app talks to. You give it the services to send to, and it passes every event on to each of them. Herald calls these services providers.

Here there's one provider, the log module. It has two parts: a tracker that handles events and user properties, and a service that handles everything else, such as start-up and sign-in.

// Where the log lines go.
val logger = AnalyticsLogger { Log.d("analytics", it) }

// Handles events and user properties, using Herald's ready-made factories.
val logTracker = LogAnalyticsTrackerService(
    eventTrackerFactory = CompositeLogEventTrackerFactory(
        ScreenViewLogEventTrackerFactory(logger), // screen views
        GenericLogEventTrackerFactory(logger),    // every other event
    ),
    propertySetterFactory = CompositeLogPropertySetterFactory(
        GenericLogPropertySetterFactory(logger),
    ),
)
// Handles the rest: start-up, sign-in and sign-out, consent.
val logService = LogAnalyticsService(logger)

val herald = Herald {
    provider(
        name = "log",
        events = logTracker,
        properties = logTracker,
        identity = logService,
        lifecycle = logService,
        consent = logService,
    )
}

Create Herald once for the whole app. Set up your app shows how to share it.

Start it in Application.onCreate(), which also runs for starts without a screen, like a push notification. start() is a suspend function, because some services set themselves up in the background, so launch it in a scope that lives as long as the app:

class App : Application() {
    // One scope for work that lives as long as the app.
    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

    // Herald, injected by your DI setup as the interface for starting analytics.
    lateinit var analytics: AnalyticsLifecycleService

    override fun onCreate() {
        super.onCreate()
        appScope.launch { analytics.start() }
    }
}
Starting from a ViewModel instead

An app with a single activity and no background work can start Herald from its first screen's ViewModel, using the ViewModel's own scope. The Moove sample app does this. If your app can start without that screen, for example from a push notification or a WorkManager job, use the Application class instead.

class MainViewModel(private val analytics: AnalyticsLifecycleService) : ViewModel() {
    init {
        viewModelScope.launch { analytics.start() }
    }
}

3. Track the event

Your classes don't use Herald directly. They ask for EventTrackerService, a small interface with a single track function, and you pass Herald in its place. That keeps them easy to test: in tests you pass a fake instead.

class CheckoutViewModel(private val analytics: EventTrackerService) {
    suspend fun onCheckout(plan: String, seats: Int) {
        analytics.track(CheckoutStarted(plan, seats))
    }
}

Call onCheckout("pro", 3), and the log shows the event as a real service would receive it:

Logcat
D/analytics: [herald] start
D/analytics: [herald] event   checkout_started
D/analytics:     ├─ plan  = pro
D/analytics:     └─ seats = 3

4. Add Firebase

First set up Firebase in your app the usual way, by following Firebase's guide for Android. Then add Herald's Firebase module:

implementation("io.github.mkhytarmkhoian:herald-firebase:1.4.0")
implementation 'io.github.mkhytarmkhoian:herald-firebase:1.4.0'

The Firebase provider is built the same way as the log one. Its factories send screen views as GA4's own screen_view event, and every other event under its own name:

fun firebaseProvider(firebaseAnalytics: FirebaseAnalytics): Herald.Provider {
    val tracker = FirebaseAnalyticsTrackerService(
        CompositeFirebaseEventTrackerFactory(
            ScreenViewFirebaseEventTrackerFactory(firebaseAnalytics), // GA4's reserved screen_view
            GenericFirebaseEventTrackerFactory(firebaseAnalytics),    // everything else, as-is
        ),
        CompositeFirebasePropertySetterFactory(
            GenericFirebasePropertySetterFactory(firebaseAnalytics),
        ),
    )
    val service = FirebaseAnalyticsService(firebaseAnalytics)
    return Herald.Provider(
        name = "firebase",
        events = tracker,
        properties = tracker,
        identity = service,
        lifecycle = service,
        consent = service,
    )
}

Add it to Herald next to the log provider:

val herald = Herald {
    provider(
        name = "log",
        events = logTracker,
        properties = logTracker,
        identity = logService,
        lifecycle = logService,
        consent = logService,
    )
    providers(listOf(firebaseProvider(firebaseAnalytics))) // the new line
}

The same CheckoutStarted now goes to both the log and Firebase, and CheckoutViewModel didn't change at all.

Next

  • Set up your app: several services, error reporting and dependency injection.
  • Android SDK: every module, and what's specific to Android.
  • Compose: screen views and impressions from composables.
  • Philosophy: why Herald works the way it does.