
Unified analytics event routing from shared view models to multiple vendors, with revenue-event translation, bridged interop for native SDKs, label-based filtering and centralized error reporting.
Herald is an analytics library for mobile apps. Your app describes what happened as an event, and Herald sends that event to every analytics service you use: Firebase, Adjust, Mixpanel, AppsFlyer, Amplitude, Meta, or one you build yourself.
This repository is Herald for Kotlin Multiplatform: write your events once, in shared Kotlin, and track them from shared view models. It wraps the two native SDKs, so each platform keeps its own setup and vendors:
HeraldInterop bridge.| herald-kmp | Android SDK (herald) |
iOS SDK (herald-ios) |
|---|---|---|
| 1.1.0 | 1.4.0 | 1.3.0 |
| 1.0.0 | 1.2.0 | 1.1.0 |
In your shared module:
kotlin {
listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared"
export("io.github.mkhytarmkhoian:herald-kmp-core:1.1.0") // so Swift can use it
}
}
sourceSets {
commonMain.dependencies {
api("io.github.mkhytarmkhoian:herald-kmp-core:1.1.0")
}
}
}For revenue, add the vendor's module too: herald-kmp-amplitude, herald-kmp-adjust,
herald-kmp-appsflyer or herald-kmp-meta, in commonMain and in export(...).
The same Kotlin as on Android:
class CheckoutStarted(plan: String) : Event {
override val name = "checkout_started"
override val parameters = parameters { put("plan", plan) }
}
class CheckoutViewModel(private val analytics: EventTrackerService) {
suspend fun started(plan: String) = analytics.track(CheckoutStarted(plan))
suspend fun bought(plan: String) = analytics.track(
amplitudeRevenueEvent(name = "purchase", price = 4.99, productId = plan, currency = "USD"),
)
}Two things differ from Android, because of Kotlin limits:
AnalyticsValues.string("dark"), not AnalyticsValue.String("dark").amplitudeRevenueEvent(…), adjustAdRevenueEvent(…), appsFlyerPurchaseEvent(…). It has the
same parameters and defaults as the Android constructor.Nothing extra. Set Herald up as in the Android guide and give it to your shared code:
val herald = Herald {
provider("amplitude", events = amplitudeTracker /* … */)
}
CheckoutViewModel(analytics = herald)herald-ios with its HeraldCore and HeraldInterop modules, and your vendors'
packages, as for any iOS app.HeraldKmpBridge.swift, as is;RevenueTranslation.swift, keeping the blocks
for your vendors. It turns the shared revenue events into herald-ios's.let herald = Herald(providers: [amplitudeProvider(amplitude)])
let bridge = HeraldBridge(herald, translate: revenueEvent, errorReporter: report)
let viewModel = CheckoutViewModel(analytics: IosAnalytics(bridge: HeraldKmpBridge(bridge)))If RevenueTranslation.swift misses a revenue event, the bridge reports it to errorReporter
instead of letting the revenue disappear without a word.
herald-ios has types with the same names as herald-kmp's, so Swift sees the Kotlin ones with
"Kotlin" in front: KotlinEvent, KotlinAmplitudeRevenueEvent and so on.
A label is an interface your events implement, such as interface PersonalDataEvent : Event.
Checks in shared Kotlin and on Android work as usual. A Swift factory on iOS checks the Kotlin
event:
if event.kotlinEvent is PersonalDataEvent {
return .dropped
}Vendor failures go to the error reporter each app sets up: in Kotlin on Android, in Swift on iOS
(on Herald and on HeraldBridge). To report them through shared Kotlin code, call it from
Swift's reporter:
let report: AnalyticsErrorReporter = { failure in
AnalyticsFailuresKt.reportAnalyticsFailure(message: "\(failure)")
}The full documentation is on the project website. The sample has a shared module, with its tests against the real Android SDK, and an iOS app that runs it on herald-ios.
See CHANGELOG.md for release notes and CONTRIBUTING.md to contribute.
Apache License 2.0. See LICENSE.
Herald is an analytics library for mobile apps. Your app describes what happened as an event, and Herald sends that event to every analytics service you use: Firebase, Adjust, Mixpanel, AppsFlyer, Amplitude, Meta, or one you build yourself.
This repository is Herald for Kotlin Multiplatform: write your events once, in shared Kotlin, and track them from shared view models. It wraps the two native SDKs, so each platform keeps its own setup and vendors:
HeraldInterop bridge.| herald-kmp | Android SDK (herald) |
iOS SDK (herald-ios) |
|---|---|---|
| 1.1.0 | 1.4.0 | 1.3.0 |
| 1.0.0 | 1.2.0 | 1.1.0 |
In your shared module:
kotlin {
listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared"
export("io.github.mkhytarmkhoian:herald-kmp-core:1.1.0") // so Swift can use it
}
}
sourceSets {
commonMain.dependencies {
api("io.github.mkhytarmkhoian:herald-kmp-core:1.1.0")
}
}
}For revenue, add the vendor's module too: herald-kmp-amplitude, herald-kmp-adjust,
herald-kmp-appsflyer or herald-kmp-meta, in commonMain and in export(...).
The same Kotlin as on Android:
class CheckoutStarted(plan: String) : Event {
override val name = "checkout_started"
override val parameters = parameters { put("plan", plan) }
}
class CheckoutViewModel(private val analytics: EventTrackerService) {
suspend fun started(plan: String) = analytics.track(CheckoutStarted(plan))
suspend fun bought(plan: String) = analytics.track(
amplitudeRevenueEvent(name = "purchase", price = 4.99, productId = plan, currency = "USD"),
)
}Two things differ from Android, because of Kotlin limits:
AnalyticsValues.string("dark"), not AnalyticsValue.String("dark").amplitudeRevenueEvent(…), adjustAdRevenueEvent(…), appsFlyerPurchaseEvent(…). It has the
same parameters and defaults as the Android constructor.Nothing extra. Set Herald up as in the Android guide and give it to your shared code:
val herald = Herald {
provider("amplitude", events = amplitudeTracker /* … */)
}
CheckoutViewModel(analytics = herald)herald-ios with its HeraldCore and HeraldInterop modules, and your vendors'
packages, as for any iOS app.HeraldKmpBridge.swift, as is;RevenueTranslation.swift, keeping the blocks
for your vendors. It turns the shared revenue events into herald-ios's.let herald = Herald(providers: [amplitudeProvider(amplitude)])
let bridge = HeraldBridge(herald, translate: revenueEvent, errorReporter: report)
let viewModel = CheckoutViewModel(analytics: IosAnalytics(bridge: HeraldKmpBridge(bridge)))If RevenueTranslation.swift misses a revenue event, the bridge reports it to errorReporter
instead of letting the revenue disappear without a word.
herald-ios has types with the same names as herald-kmp's, so Swift sees the Kotlin ones with
"Kotlin" in front: KotlinEvent, KotlinAmplitudeRevenueEvent and so on.
A label is an interface your events implement, such as interface PersonalDataEvent : Event.
Checks in shared Kotlin and on Android work as usual. A Swift factory on iOS checks the Kotlin
event:
if event.kotlinEvent is PersonalDataEvent {
return .dropped
}Vendor failures go to the error reporter each app sets up: in Kotlin on Android, in Swift on iOS
(on Herald and on HeraldBridge). To report them through shared Kotlin code, call it from
Swift's reporter:
let report: AnalyticsErrorReporter = { failure in
AnalyticsFailuresKt.reportAnalyticsFailure(message: "\(failure)")
}The full documentation is on the project website. The sample has a shared module, with its tests against the real Android SDK, and an iOS app that runs it on herald-ios.
See CHANGELOG.md for release notes and CONTRIBUTING.md to contribute.
Apache License 2.0. See LICENSE.