Я — Денис, 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.
