diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index a6ddd5c799..781af4c36e 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -120,7 +120,7 @@ ext.groups = [ 'com.parse.bolts', 'com.pinterest', 'com.pinterest.ktlint', - 'com.posthog.android', + 'com.posthog', 'com.squareup', 'com.squareup.curtains', 'com.squareup.duktape', diff --git a/vector/build.gradle b/vector/build.gradle index 441dbd3ec0..69a6691edd 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -231,9 +231,7 @@ dependencies { kapt libs.dagger.hiltCompiler // Analytics - implementation('com.posthog.android:posthog:2.0.3') { - exclude group: 'com.android.support', module: 'support-annotations' - } + implementation 'com.posthog:posthog-android:3.2.0' implementation libs.sentry.sentryAndroid // UnifiedPush diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt index acc6ebf51e..8520a40ca2 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt @@ -16,9 +16,7 @@ package im.vector.app.features.analytics.impl -import com.posthog.android.Options -import com.posthog.android.PostHog -import com.posthog.android.Properties +import com.posthog.PostHogInterface import im.vector.app.core.di.NamedGlobalScope import im.vector.app.features.analytics.AnalyticsConfig import im.vector.app.features.analytics.VectorAnalytics @@ -36,9 +34,6 @@ import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -private val REUSE_EXISTING_ID: String? = null -private val IGNORED_OPTIONS: Options? = null - @Singleton class DefaultVectorAnalytics @Inject constructor( private val postHogFactory: PostHogFactory, @@ -49,9 +44,9 @@ class DefaultVectorAnalytics @Inject constructor( @NamedGlobalScope private val globalScope: CoroutineScope ) : VectorAnalytics { - private var posthog: PostHog? = null + private var posthog: PostHogInterface? = null - private fun createPosthog(): PostHog? { + private fun createPosthog(): PostHogInterface? { return when { analyticsConfig.isEnabled -> postHogFactory.createPosthog() else -> { @@ -126,7 +121,7 @@ class DefaultVectorAnalytics @Inject constructor( posthog?.reset() } else { Timber.tag(analyticsTag.value).d("identify") - posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS) + posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties()) } } @@ -155,7 +150,7 @@ class DefaultVectorAnalytics @Inject constructor( when (_userConsent) { true -> { posthog = createPosthog() - posthog?.optOut(false) + posthog?.optIn() identifyPostHog() pendingUserProperties?.let { doUpdateUserProperties(it) } pendingUserProperties = null @@ -163,8 +158,8 @@ class DefaultVectorAnalytics @Inject constructor( false -> { // When opting out, ensure that the queue is flushed first, or it will be flushed later (after user has revoked consent) posthog?.flush() - posthog?.optOut(true) - posthog?.shutdown() + posthog?.optOut() + posthog?.close() posthog = null } } @@ -177,6 +172,7 @@ class DefaultVectorAnalytics @Inject constructor( ?.takeIf { userConsent == true } ?.capture( event.getName(), + analyticsId, event.getProperties()?.toPostHogProperties() ) } @@ -197,27 +193,37 @@ class DefaultVectorAnalytics @Inject constructor( } private fun doUpdateUserProperties(userProperties: UserProperties) { + // we need a distinct id to set user properties + val distinctId = analyticsId ?: return posthog ?.takeIf { userConsent == true } - ?.identify(REUSE_EXISTING_ID, userProperties.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS) + ?.identify(distinctId, userProperties.getProperties()) } - private fun Map?.toPostHogProperties(): Properties? { + private fun Map?.toPostHogProperties(): Map? { if (this == null) return null - return Properties().apply { - putAll(this@toPostHogProperties) + val nonNulls = HashMap() + this.forEach { (key, value) -> + if (value != null) { + nonNulls[key] = value + } } + return nonNulls } /** * We avoid sending nulls as part of the UserProperties as this will reset the values across all devices. * The UserProperties event has nullable properties to allow for clients to opt in. */ - private fun Map.toPostHogUserProperties(): Properties { - return Properties().apply { - putAll(this@toPostHogUserProperties.filter { it.value != null }) + private fun Map.toPostHogUserProperties(): Map { + val nonNulls = HashMap() + this.forEach { (key, value) -> + if (value != null) { + nonNulls[key] = value + } } + return nonNulls } override fun trackError(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt index 7442989352..cbb6b9dcfd 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt @@ -17,7 +17,9 @@ package im.vector.app.features.analytics.impl import android.content.Context -import com.posthog.android.PostHog +import com.posthog.PostHogInterface +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig import im.vector.app.core.resources.BuildMeta import im.vector.app.features.analytics.AnalyticsConfig import javax.inject.Inject @@ -28,29 +30,17 @@ class PostHogFactory @Inject constructor( private val buildMeta: BuildMeta, ) { - fun createPosthog(): PostHog { - return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost) - // Record certain application events automatically! (off/false by default) - // .captureApplicationLifecycleEvents() - // Record screen views automatically! (off/false by default) - // .recordScreenViews() - // Capture deep links as part of the screen call. (off by default) - // .captureDeepLinks() - // Maximum number of events to keep in queue before flushing (default 20) - // .flushQueueSize(20) - // Max delay before flushing the queue (30 seconds) - // .flushInterval(30, TimeUnit.SECONDS) - // Enable or disable collection of ANDROID_ID (true) - .collectDeviceId(false) - .logLevel(getLogLevel()) - .build() - } - - private fun getLogLevel(): PostHog.LogLevel { - return if (buildMeta.isDebug) { - PostHog.LogLevel.DEBUG - } else { - PostHog.LogLevel.INFO + fun createPosthog(): PostHogInterface { + val config = PostHogAndroidConfig( + apiKey = analyticsConfig.postHogApiKey, + host = analyticsConfig.postHogHost, + // we do that manually + captureScreenViews = false, + ).also { + if (buildMeta.isDebug) { + it.debug = true + } } + return PostHogAndroid.with(context, config) } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt index 1d18c97d32..3650aa6bba 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt @@ -16,12 +16,12 @@ package im.vector.app.test.fakes -import com.posthog.android.PostHog +import com.posthog.PostHogInterface import im.vector.app.features.analytics.impl.PostHogFactory import io.mockk.every import io.mockk.mockk -class FakePostHogFactory(postHog: PostHog) { +class FakePostHogFactory(postHog: PostHogInterface) { val instance = mockk().also { every { it.createPosthog() } returns postHog }