Kotlin Multiplatform notes¶
Shared code works the same as on Android. These are the places where it differs.
Values and revenue events¶
Two things differ from Android, because of Kotlin limits:
- A property's value is
AnalyticsValues.string("dark"), notAnalyticsValue.String("dark"). - A revenue event is made by the function named after its class, with a small first letter. It has the same parameters and defaults as the Android constructor:
package io.github.mkhytarmkhoian.herald.sample
import io.github.mkhytarmkhoian.herald.kmp.EventTrackerService
import io.github.mkhytarmkhoian.herald.kmp.adjust.adjustAdRevenueEvent
import io.github.mkhytarmkhoian.herald.kmp.adjust.adjustRevenueEvent
import io.github.mkhytarmkhoian.herald.kmp.amplitude.amplitudeRevenueEvent
import io.github.mkhytarmkhoian.herald.kmp.appsflyer.MediationNetwork
import io.github.mkhytarmkhoian.herald.kmp.appsflyer.appsFlyerAdRevenueEvent
import io.github.mkhytarmkhoian.herald.kmp.appsflyer.appsFlyerPurchaseEvent
import io.github.mkhytarmkhoian.herald.kmp.appsflyer.appsFlyerSubscribeEvent
import io.github.mkhytarmkhoian.herald.kmp.meta.metaPurchaseEvent
import io.github.mkhytarmkhoian.herald.kmp.parameters
// Written once. Each vendor gets its revenue on both platforms.
public class StoreViewModel(private val analytics: EventTrackerService) {
public suspend fun bought(plan: String) {
val source = parameters { put("source", "paywall") }
analytics.track(
amplitudeRevenueEvent(
name = "purchase", price = 4.99, productId = plan, currency = "USD",
parameters = source,
),
)
analytics.track(
adjustRevenueEvent(
name = "purchase", revenue = 4.99, currency = "USD", deduplicationId = "order-1",
),
)
analytics.track(
appsFlyerPurchaseEvent(
name = "af_purchase", revenue = 4.99, currency = "USD", contentId = plan,
quantity = 1,
),
)
analytics.track(
appsFlyerSubscribeEvent(name = "af_subscribe", revenue = 4.99, currency = "USD"),
)
analytics.track(metaPurchaseEvent(name = "purchase", amount = 4.99, currency = "USD"))
}
public suspend fun adShown() {
analytics.track(
adjustAdRevenueEvent(
name = "ad_revenue", source = "applovin_max_sdk", revenue = 0.01, currency = "USD",
adRevenueNetwork = "AppLovin",
),
)
analytics.track(
appsFlyerAdRevenueEvent(
name = "ad_revenue", monetizationNetwork = "AppLovin",
mediationNetwork = MediationNetwork.APPLOVIN_MAX, revenue = 0.01, currency = "USD",
),
)
}
}
Names in Swift¶
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.
Labels¶
A label is an interface your events implement. Checks in shared Kotlin and on Android work as usual:
package io.github.mkhytarmkhoian.herald.sample
import io.github.mkhytarmkhoian.herald.kmp.AnalyticsValue
import io.github.mkhytarmkhoian.herald.kmp.AnalyticsValues
import io.github.mkhytarmkhoian.herald.kmp.Event
import io.github.mkhytarmkhoian.herald.kmp.EventTrackerService
import io.github.mkhytarmkhoian.herald.kmp.Property
import io.github.mkhytarmkhoian.herald.kmp.PropertyTrackerService
import io.github.mkhytarmkhoian.herald.kmp.parameters
/** A label: events about a person. Some vendors must never get them. */
public interface PersonalDataEvent : Event
public class ProfileOpened(userId: String) : PersonalDataEvent {
override val name: String = "profile_opened"
override val parameters: Map<String, AnalyticsValue> = parameters { put("user_id", userId) }
}
public class AppTheme(dark: Boolean) : Property {
override val name: String = "app_theme"
override val value: AnalyticsValue = AnalyticsValues.string(if (dark) "dark" else "light")
}
public class ProfileViewModel(
private val analytics: EventTrackerService,
private val properties: PropertyTrackerService,
) {
public suspend fun opened(userId: String): Unit = analytics.track(ProfileOpened(userId))
public suspend fun themeChanged(dark: Boolean): Unit = properties.set(AppTheme(dark))
}
A factory written in Swift checks the Kotlin event:
import HeraldCore
import HeraldLog
import Shared
/// An iOS rule, written in Swift: personal data never reaches this log. The label is the shared
/// Kotlin one, checked by type.
struct NoPersonalDataLogFactory: LogEventTrackerFactory {
func create(_ event: any Event) throws -> Resolution<any LogEventTracker> {
if event.kotlinEvent is PersonalDataEvent {
return .dropped
}
return .declined
}
}
Errors¶
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: