Я — Денис, Middle Android-разработчик в «Black Bricks». В этой статье я расскажу о том как я подключил Firebase Analytics в KMP проект для Android, iOS, Desktop (MacOS, Windows).
Раньше мне приходилось подключать аналитику в проекты, но это всегда были нативные мобильные приложения. И я сильно удивился, когда не увидел в Firebase консоли:
поддержки Desktop;
поддержки KMP.
Я начал ресёрчинг библиотек и инструментов, похожих на Firebase. На самом деле не очень хотелось использовать сервис, отличный от Firebase, потому что за много лет уже многие привыкли к его интерфейсу и возможностям. Я искал альтернативы, посмотрел Mixpanel, Flurry, Amplitude — но все они, в любом случае, были в основном также только для мобильных телефонов и показались чуть сложнее, чем привычный Firebase.
Тут я наткнулся на KMP-библиотеку, обёртку над популярными сервисами Firebase. Но аналитики тут я не нашёл. Я посмотрел, как организован интероп Crashlytics в этой библиотеке и даже попытался написать нечто похожее для моей задачи. Увы, не вышло.
На второй день ресёрча я просто создал 4 проекта в Firebase под Desktop MacOS, Desktop Windows, Android и iOS. Уже отчаявшись, нашёл способ взаимодействия с Firebase через их REST API — этот способ как раз отлично подошёл для моей задачи. Далее подробнее о нём расскажу.
Как уже писал ранее, нужно было создать 4 приложения в рамках одной консоли под все ОС, которые мы поддерживаем на проекте. Для этого решения нам даже не нужно добавлять google-services.json файлы в проект, если вы, конечно, не используете что-то ещё кроме аналитики. Далее дело за малым — написать REST код для аналитики.
Базовая семантика двух функций, которые отсылают события в аналитику с параметрами или без.
expect suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent, params: Map<FirebaseAnalyticsParameter, String>)
expect suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent)
Под каждую платформу нужно написать реализацию этих функций, чуть проще это сделать для мобилок, просто вызываем нужные UseCase.
actual suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent, params: Map<FirebaseAnalyticsParameter, String>) {
SendAndroidAnalyticsEventUseCase()
.execute(event = event, params = params)
}
actual suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent) {
SendAndroidAnalyticsEventUseCase()
.execute(event = event)
}
Для разделения аналитики на Desktop под MacOS и Windows — я написал метод для определения под какой ОС он запущен.
actual fun getPlatform(): Platform = object : Platform {
override val name: String
get() {
val osName = System.getProperty("os.name").lowercase(Locale.ROOT)
return when {
osName.contains("mac") -> "macOS"
osName.contains("win") -> "Windows"
else -> "Unknown"
}
}
}
Сам UseCase вызывает REST API метод и варьирует полями, о которых чуть позже.
class SendIphoneAnalyticsEventUseCase : KoinComponent {
private val firebaseAnalyticsRepo: FirebaseAnalyticsRepo = get()
@NativeCoroutines
suspend fun execute(
event: FirebaseAnalyticsEvent,
params: Map<FirebaseAnalyticsParameter, String>? = null,
) {
firebaseAnalyticsRepo.sendIphoneEvent(
event = Event(
name = event.eventName,
params = params?.mapKeys { it.key.paramName },
),
)
}
}
Для REST запросов я использую Ktorfit, это обёртка над Ktor — вполне удобно использовать в KMP.
private const val BASE_FIREBASE_URL = "https://www.google-analytics.com/mp/collect"
interface AnalyticsApi {
@POST(BASE_FIREBASE_URL)
@Headers(
value = [
"${ParseHeaders.CONTENT_TYPE}: application/json",
],
)
suspend fun logEvent(
@Query("firebase_app_id") firebaseAppId: String,
@Query("api_secret") apiSecret: String,
@Body events: FirebaseEvent,
)
}
1. clientId
— всегда одинаковый, можно посмотреть в карточке проекта в поле Project number;
2. apiSecret
— уникальное значение для каждого проекта, создаётся вручную в аккаунте Google аналитики;
3. firebaseAppId
— уникальное значение для каждого проекта, можно взять из поля mobilesdk_app_id
в google-services.json
.
Структура, которую отправляю по REST:
@Serializable
data class FirebaseEvent(
@SerialName("client_id")
val clientId: String,
val events: List<Event>,
)
@Serializable
data class Event(
val name: String,
val params: Map<String, String>? = null,
)
Для каждого UseCase я подставляю разные значения этих полей из BuildConfig. А сами методы аналитики вызываю в UI или бизнес-логике:
LaunchedEffect(Unit) {
logFirebaseEvent(
event = FirebaseAnalyticsEvent.Event,
params = mapOf(FirebaseAnalyticsParameter.Param to param),
)
}
Спасибо за чтение!
Денис Попков
Middle Android разработчик в «Black Bricks»
Если вы нашли неточности/ошибки в статье или просто хотите дополнить её своим мнением — то прошу в комментарии! Или можете написать мне в Telegram — t.me/MolodoyDenis.
ArtyomZhukov
Спасибо за статью! Как раз искал что-то подобное для своего проекта под Android & Desktop