Firebase Analytics в KMP: Android, iOS, Desktop (MacOS, Windows)

Я — Денис, Middle Android-разработчик в «Black Bricks». В этой статье я расскажу о том как я подключил Firebase Analytics в KMP проект для Android, iOS, Desktop (MacOS, Windows).

Раньше мне приходилось подключать аналитику в проекты, но это всегда были нативные мобильные приложения. И я сильно удивился, когда не увидел в Firebase консоли:

  1. поддержки Desktop;

  2. поддержки KMP.

Я начал ресёрчинг библиотек и инструментов, похожих на Firebase. На самом деле не очень хотелось использовать сервис, отличный от Firebase, потому что за много лет уже многие привыкли к его интерфейсу и возможностям. Я искал альтернативы, посмотрел Mixpanel, Flurry, Amplitude —, но все они, в любом случае, были в основном также только для мобильных телефонов и показались чуть сложнее, чем привычный Firebase.

Тут я наткнулся на KMP-библиотеку, обёртку над популярными сервисами Firebase. Но аналитики тут я не нашёл. Я посмотрел, как организован интероп Crashlytics в этой библиотеке и даже попытался написать нечто похожее для моей задачи. Увы, не вышло.

Библиотека-обёртка над сервисами Firebase

Библиотека-обёртка над сервисами Firebase

На второй день ресёрча я просто создал 4 проекта в Firebase под Desktop MacOS, Desktop Windows, Android и iOS. Уже отчаявшись, нашёл способ взаимодействия с Firebase через их REST API — этот способ как раз отлично подошёл для моей задачи. Далее подробнее о нём расскажу.

Как уже писал ранее, нужно было создать 4 приложения в рамках одной консоли под все ОС, которые мы поддерживаем на проекте. Для этого решения нам даже не нужно добавлять google-services.json файлы в проект, если вы, конечно, не используете что-то ещё кроме аналитики. Далее дело за малым — написать REST код для аналитики.

Базовая семантика двух функций, которые отсылают события в аналитику с параметрами или без.

expect suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent, params: Map)
expect suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent)

Под каждую платформу нужно написать реализацию этих функций, чуть проще это сделать для мобилок, просто вызываем нужные UseCase.

actual suspend fun logFirebaseEvent(event: FirebaseAnalyticsEvent, params: Map) {
     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? = 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,
 )

 @Serializable
 data class Event(
     val name: String,
     val params: Map? = null,
 )

Для каждого UseCase я подставляю разные значения этих полей из BuildConfig. А сами методы аналитики вызываю в UI или бизнес-логике:

LaunchedEffect(Unit) {
    logFirebaseEvent(
        event = FirebaseAnalyticsEvent.Event,
        params = mapOf(FirebaseAnalyticsParameter.Param to param),
    )
}

Спасибо за чтение!

dcddba896ec4c949dec4aa0bb59a0a4b.jpgДенис Попков

Middle Android разработчик в «Лайв Тайпинге»

Если вы нашли неточности/ошибки в статье или просто хотите дополнить её своим мнением — то прошу в комментарии! Или можете написать мне в Telegram — t.me/MolodoyDenis.

© Habrahabr.ru