Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-01 19:40:54 +04:00
parent 94703ae459
commit 34ffa9a1e7
20 changed files with 158 additions and 21 deletions

View file

@ -74,6 +74,7 @@ dependencies {
implementation(projects.data.tokens) implementation(projects.data.tokens)
implementation(projects.data.txhistory) implementation(projects.data.txhistory)
implementation(projects.data.wallets) implementation(projects.data.wallets)
implementation(projects.data.analytics)
/** Features */ /** Features */
implementation(projects.features.onboarding) implementation(projects.features.onboarding)

View file

@ -14,6 +14,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.api.common.MoshiConverter import com.tangem.datasource.api.common.MoshiConverter
@ -216,6 +217,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject @Inject
lateinit var sendFeatureToggles: SendFeatureToggles lateinit var sendFeatureToggles: SendFeatureToggles
@Inject
lateinit var oneTimeEventFilter: OneTimeEventFilter
// endregion Injected // endregion Injected
override fun onCreate() { override fun onCreate() {
@ -353,6 +357,8 @@ internal class TapApplication : Application(), ImageLoaderFactory {
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
factory.addFilter(oneTimeEventFilter)
val buildData = AnalyticsHandlerBuilder.Data( val buildData = AnalyticsHandlerBuilder.Data(
application = application, application = application,
config = config, config = config,

View file

@ -9,8 +9,8 @@ class AmplitudeAnalyticsHandler(
override fun id(): String = ID override fun id(): String = ID
override fun send(event: String, params: Map<String, String>) { override fun send(eventId: String, params: Map<String, String>) {
client.logEvent(event, params) client.logEvent(eventId, params)
} }
companion object { companion object {

View file

@ -12,8 +12,8 @@ class AppsFlyerAnalyticsHandler(
override fun id(): String = ID override fun id(): String = ID
override fun send(event: String, params: Map<String, String>) { override fun send(eventId: String, params: Map<String, String>) {
client.logEvent(event, params) client.logEvent(eventId, params)
} }
override fun send(event: AnalyticsEvent) { override fun send(event: AnalyticsEvent) {

View file

@ -14,8 +14,8 @@ class FirebaseAnalyticsHandler(
override fun id(): String = ID override fun id(): String = ID
override fun send(event: String, params: Map<String, String>) { override fun send(eventId: String, params: Map<String, String>) {
client.logEvent(event, params) client.logEvent(eventId, params)
} }
override fun send(event: AnalyticsEvent) { override fun send(event: AnalyticsEvent) {

View file

@ -1,19 +1,18 @@
plugins { plugins {
alias(deps.plugins.android.library) alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt) alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration") id("configuration")
} }
dependencies { dependencies {
/** DI */ /** DI */
implementation(deps.hilt.android) implementation(deps.hilt.core)
kapt(deps.hilt.kapt) kapt(deps.hilt.kapt)
/** Core shouldn't depends on core, but in case with utils and logging its necessary */ /** Core shouldn't depends on core, but in case with utils and logging its necessary */
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.analytics.models) implementation(projects.core.analytics.models)
implementation(deps.kotlin.coroutines)
} }

View file

@ -8,4 +8,7 @@ open class AnalyticsEvent(
val event: String, val event: String,
var params: Map<String, String> = mapOf(), var params: Map<String, String> = mapOf(),
val error: Throwable? = null, val error: Throwable? = null,
) ) {
val id: String = "[$category] $event"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.analytics.models
interface OneTimeAnalyticsEvent {
val oneTimeEventId: String
}

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.tangem.core.analytics" />

View file

@ -16,7 +16,7 @@ interface AnalyticsEventFilter {
* An internal filter check that, on external or internal conditions, recognizes the possibility of * An internal filter check that, on external or internal conditions, recognizes the possibility of
* sending an event. * sending an event.
*/ */
fun canBeSent(event: AnalyticsEvent): Boolean suspend fun canBeSent(event: AnalyticsEvent): Boolean
/** /**
* Performs a check to see if the event can be dispatched by a specific handler * Performs a check to see if the event can be dispatched by a specific handler

View file

@ -12,13 +12,11 @@ interface AnalyticsEventHandler {
interface AnalyticsHandler : AnalyticsEventHandler { interface AnalyticsHandler : AnalyticsEventHandler {
fun id(): String fun id(): String
fun send(event: String, params: Map<String, String> = emptyMap()) fun send(eventId: String, params: Map<String, String> = emptyMap())
override fun send(event: AnalyticsEvent) { override fun send(event: AnalyticsEvent) {
send(prepareEventString(event), event.params) send(event.id, event.params)
} }
fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}"
} }
interface ErrorEventHandler { interface ErrorEventHandler {

View file

@ -2,6 +2,8 @@ package com.tangem.core.analytics.di
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.analytics.repository.AnalyticsRepository
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -10,11 +12,16 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
class AnalyticsModule { internal object AnalyticsModule {
@Singleton @Singleton
@Provides @Provides
fun provideAnalyticsHandler(): AnalyticsEventHandler { fun provideAnalyticsHandler(): AnalyticsEventHandler {
return Analytics // todo replace after refactoring calling Analytics in whole project return Analytics // todo replace after refactoring calling Analytics in whole project
} }
@Provides
fun provideOneTimeEventFilter(analyticsRepository: AnalyticsRepository): OneTimeEventFilter {
return OneTimeEventFilter(analyticsRepository)
}
} }

View file

@ -0,0 +1,30 @@
package com.tangem.core.analytics.filter
import com.tangem.core.analytics.api.AnalyticsEventFilter
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.OneTimeAnalyticsEvent
import com.tangem.core.analytics.repository.AnalyticsRepository
class OneTimeEventFilter(
private val analyticsRepository: AnalyticsRepository,
) : AnalyticsEventFilter {
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is OneTimeAnalyticsEvent
override suspend fun canBeSent(event: AnalyticsEvent): Boolean {
if (event !is OneTimeAnalyticsEvent) return true
val isSent = analyticsRepository.checkIsEventSent(event.oneTimeEventId)
if (!isSent) {
analyticsRepository.setIsEventSent(event.oneTimeEventId)
}
return !isSent
}
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
return canBeAppliedTo(event)
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.analytics.repository
interface AnalyticsRepository {
suspend fun checkIsEventSent(eventId: String): Boolean
suspend fun setIsEventSent(eventId: String)
}

View file

@ -34,6 +34,8 @@ object PreferencesKeys {
val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") } val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") }
val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") } val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") }
val SENT_ONE_TIME_EVENTS_KEY by lazy { stringPreferencesKey(name = "sentOneTimeEvents") }
} }
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */ /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */

1
data/analytics/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/buld

View file

@ -0,0 +1,32 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
id("configuration")
}
android {
namespace = "com.tangem.data.analytics"
}
dependencies {
/** Project - Analytics */
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
/** Project - Data */
implementation(projects.core.datasource)
implementation(projects.data.common)
/** AndroidX */
implementation(deps.androidx.datastore)
/** DI */
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)
/** Other */
implementation(deps.kotlin.coroutines)
implementation(deps.timber)
}

View file

@ -0,0 +1,27 @@
package com.tangem.data.analytics
import com.tangem.core.analytics.repository.AnalyticsRepository
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectListSync
internal class DefaultAnalyticsRepository(
private val appPreferencesStore: AppPreferencesStore,
) : AnalyticsRepository {
override suspend fun checkIsEventSent(eventId: String): Boolean {
val sentEvents = appPreferencesStore
.getObjectListSync<String>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY)
return eventId in sentEvents
}
override suspend fun setIsEventSent(eventId: String) {
appPreferencesStore.editData { mutablePreferences ->
val sentEvents = mutablePreferences.getObject<List<String>>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY)
val updatedSentEvents = sentEvents.orEmpty() + eventId
mutablePreferences.setObject(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents)
}
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.data.analytics.di
import com.tangem.core.analytics.repository.AnalyticsRepository
import com.tangem.data.analytics.DefaultAnalyticsRepository
import com.tangem.datasource.local.preferences.AppPreferencesStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal object AnalyticsDataModule {
@Provides
fun provideAnalyticsRepository(appPreferencesStore: AppPreferencesStore): AnalyticsRepository {
return DefaultAnalyticsRepository(appPreferencesStore)
}
}

View file

@ -128,7 +128,6 @@ include(":domain:app-theme:models")
include(":domain:balance-hiding") include(":domain:balance-hiding")
include(":domain:balance-hiding:models") include(":domain:balance-hiding:models")
include(":domain:transaction") include(":domain:transaction")
// endregion Domain modules // endregion Domain modules
// region Data modules // region Data modules
@ -142,4 +141,5 @@ include(":data:source:preferences")
include(":data:settings") include(":data:settings")
include(":data:txhistory") include(":data:txhistory")
include(":data:wallets") include(":data:wallets")
include(":data:analytics")
// endregion Data modules // endregion Data modules