From 89101d8d89a71f8edad09b619c1e44f365eb80ef Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 21 Sep 2023 11:36:05 +0200 Subject: [PATCH] add group fixes --- .../com/posthog/android/PostHogAndroid.kt | 4 +- .../internal/PostHogAppInstallIntegration.kt | 4 +- .../internal/PostHogSharedPreferences.kt | 62 ++++++++++++------- .../posthog/android/sample/MainActivity.kt | 8 ++- posthog-v3/posthog/api/posthog.api | 8 ++- .../src/main/java/com/posthog/PostHog.kt | 47 +++++++++++--- .../main/java/com/posthog/PostHogConfig.kt | 5 +- .../src/main/java/com/posthog/PostHogEvent.kt | 2 +- .../java/com/posthog/PostHogPreferences.kt | 2 +- .../posthog/internal/PostHogFeatureFlags.kt | 6 ++ .../internal/PostHogMemoryPreferences.kt | 21 +++++-- 11 files changed, 124 insertions(+), 45 deletions(-) diff --git a/posthog-v3/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt b/posthog-v3/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt index b14e7e9b..8c981d0f 100644 --- a/posthog-v3/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt +++ b/posthog-v3/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt @@ -39,8 +39,8 @@ public class PostHogAndroid private constructor() { val path = File(context.cacheDir, "posthog-disk-queue") config.legacyStoragePrefix = config.legacyStoragePrefix ?: legacyPath.absolutePath config.storagePrefix = config.storagePrefix ?: path.absolutePath - val preferences = config.preferences ?: PostHogSharedPreferences(context, config) - config.preferences = preferences + val preferences = config.cachePreferences ?: PostHogSharedPreferences(context, config) + config.cachePreferences = preferences config.networkStatus = config.networkStatus ?: PostHogAndroidNetworkStatus(context) if (context is Application) { diff --git a/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt b/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt index bec78168..04f41dfc 100644 --- a/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt +++ b/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogAppInstallIntegration.kt @@ -8,7 +8,7 @@ import com.posthog.PostHogIntegration internal class PostHogAppInstallIntegration(private val context: Context, private val config: PostHogConfig) : PostHogIntegration { override fun install() { getPackageInfo(context, config)?.let { packageInfo -> - config.preferences?.let { preferences -> + config.cachePreferences?.let { preferences -> val versionName = packageInfo.versionName val versionCode = packageInfo.versionCodeCompat() @@ -36,6 +36,8 @@ internal class PostHogAppInstallIntegration(private val context: Context, privat preferences.setValue("version", versionName) preferences.setValue("build", versionCode) + // TODO: do we need ot send an event every time as an update? we need to compare the Ids maybe? + // maybe it didnt change PostHog.capture(event, properties = props) } } diff --git a/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt b/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt index 8c3c4828..b2e0e96c 100644 --- a/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt +++ b/posthog-v3/posthog-android/src/main/java/com/posthog/android/internal/PostHogSharedPreferences.kt @@ -10,37 +10,57 @@ internal class PostHogSharedPreferences(context: Context, config: PostHogConfig) private val sharedPreferences = context.getSharedPreferences("posthog-android-${config.apiKey}", MODE_PRIVATE) + private val lock = Any() + override fun getValue(key: String, defaultValue: Any?): Any? { - return sharedPreferences.all[key] ?: defaultValue + synchronized(lock) { + return sharedPreferences.all[key] ?: defaultValue + } } override fun setValue(key: String, value: Any) { val edit = sharedPreferences.edit() - when (value) { - is Boolean -> { - edit.putBoolean(key, value) - } - is String -> { - edit.putString(key, value) - } - is Float -> { - edit.putFloat(key, value) - } - is Long -> { - edit.putLong(key, value) - } - is Int -> { - edit.putInt(key, value) + synchronized(lock) { + when (value) { + is Boolean -> { + edit.putBoolean(key, value) + } + + is String -> { + edit.putString(key, value) + } + + is Float -> { + edit.putFloat(key, value) + } + + is Long -> { + edit.putLong(key, value) + } + + is Int -> { + edit.putInt(key, value) + } } - } - edit.apply() + edit.apply() + } } - override fun clear() { + override fun clear(except: List) { val edit = sharedPreferences.edit() - edit.clear() - edit.apply() + + synchronized(lock) { + val it = sharedPreferences.all.iterator() + while (it.hasNext()) { + val entry = it.next() + if (!except.contains(entry.key)) { + edit.remove(entry.key) + } + } + + edit.apply() + } } } diff --git a/posthog-v3/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt b/posthog-v3/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt index cf1a38cf..31149990 100644 --- a/posthog-v3/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt +++ b/posthog-v3/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt @@ -40,13 +40,15 @@ fun Greeting(name: String, modifier: Modifier = Modifier) { onClick = { // PostHog.optOut() // PostHog.optIn() - PostHog.identify("my_distinct_id", properties = mapOf("my_property" to 1), userProperties = mapOf("name" to "hello")) -// PostHog.capture("testEvent", mapOf("testProperty" to "testValue")) -// PostHog.optIn() +// PostHog.identify("my_distinct_id", properties = mapOf("my_property" to 1), userProperties = mapOf("name" to "hello")) // PostHog.capture("testEvent", mapOf("testProperty" to "testValue")) // PostHog.reloadFeatureFlagsRequest() // PostHog.isFeatureEnabled("sessionRecording") + val props = mutableMapOf() + props["test_key"] = "test_value" + PostHog.group("theType", "theKey", groupProperties = props) // PostHog.flush() +// PostHog.reset() }, ) } diff --git a/posthog-v3/posthog/api/posthog.api b/posthog-v3/posthog/api/posthog.api index 641a8ac8..12b8e6ea 100644 --- a/posthog-v3/posthog/api/posthog.api +++ b/posthog-v3/posthog/api/posthog.api @@ -35,6 +35,8 @@ public final class com/posthog/PostHog$Companion { public static synthetic fun getFeatureFlag$default (Lcom/posthog/PostHog$Companion;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; public final fun getFeatureFlagPayload (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; public static synthetic fun getFeatureFlagPayload$default (Lcom/posthog/PostHog$Companion;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; + public final fun group (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public static synthetic fun group$default (Lcom/posthog/PostHog$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V public final fun identify (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V public static synthetic fun identify$default (Lcom/posthog/PostHog$Companion;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)V public final fun isFeatureEnabled (Ljava/lang/String;Z)Z @@ -51,6 +53,7 @@ public final class com/posthog/PostHogConfig { public fun (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogDataMode;Lcom/posthog/PostHogEncryption;Ljava/util/List;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogDataMode;Lcom/posthog/PostHogEncryption;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getApiKey ()Ljava/lang/String; + public final fun getCachePreferences ()Lcom/posthog/PostHogPreferences; public final fun getContext ()Lcom/posthog/PostHogContext; public final fun getDataMode ()Lcom/posthog/PostHogDataMode; public final fun getDebug ()Z @@ -65,12 +68,12 @@ public final class com/posthog/PostHogConfig { public final fun getMaxQueueSize ()I public final fun getNetworkStatus ()Lcom/posthog/internal/PostHogNetworkStatus; public final fun getOptOut ()Z - public final fun getPreferences ()Lcom/posthog/PostHogPreferences; public final fun getPreloadFeatureFlags ()Z public final fun getSdkName ()Ljava/lang/String; public final fun getSdkVersion ()Ljava/lang/String; public final fun getSendFeatureFlagEvent ()Z public final fun getStoragePrefix ()Ljava/lang/String; + public final fun setCachePreferences (Lcom/posthog/PostHogPreferences;)V public final fun setContext (Lcom/posthog/PostHogContext;)V public final fun setDataMode (Lcom/posthog/PostHogDataMode;)V public final fun setDebug (Z)V @@ -83,7 +86,6 @@ public final class com/posthog/PostHogConfig { public final fun setMaxQueueSize (I)V public final fun setNetworkStatus (Lcom/posthog/internal/PostHogNetworkStatus;)V public final fun setOptOut (Z)V - public final fun setPreferences (Lcom/posthog/PostHogPreferences;)V public final fun setPreloadFeatureFlags (Z)V public final fun setSdkName (Ljava/lang/String;)V public final fun setSdkVersion (Ljava/lang/String;)V @@ -145,7 +147,7 @@ public abstract interface class com/posthog/PostHogLogger { } public abstract interface class com/posthog/PostHogPreferences { - public abstract fun clear ()V + public abstract fun clear (Ljava/util/List;)V public abstract fun getValue (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun setValue (Ljava/lang/String;Ljava/lang/Object;)V } diff --git a/posthog-v3/posthog/src/main/java/com/posthog/PostHog.kt b/posthog-v3/posthog/src/main/java/com/posthog/PostHog.kt index 758fedca..2cb7cc4b 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/PostHog.kt @@ -29,15 +29,15 @@ public class PostHog private constructor() { return } - val preferences = config.preferences ?: PostHogMemoryPreferences() - config.preferences = preferences + val preferences = config.cachePreferences ?: PostHogMemoryPreferences() + config.cachePreferences = preferences sessionManager = PostHogSessionManager(preferences) val serializer = PostHogSerializer(config) val api = PostHogApi(config, serializer) val queue = PostHogQueue(config, api, serializer) val featureFlags = PostHogFeatureFlags(config, api) - val optOut = config.preferences?.getValue("opt-out", defaultValue = false) as? Boolean + val optOut = config.cachePreferences?.getValue("opt-out", defaultValue = false) as? Boolean optOut?.let { config.optOut = optOut } @@ -103,7 +103,18 @@ public class PostHog private constructor() { props.putAll(it) } - // TODO: $feature/* properties, $active_feature_flags array + if (config?.sendFeatureFlagEvent == true) { + featureFlags?.getFeatureFlags()?.let { + if (it.isNotEmpty()) { + val keys = mutableListOf() + for (entry in it.entries) { + props["\$feature/${entry.key}"] = entry.value + keys.add(entry.key) + } + props["\$active_feature_flags"] = keys + } + } + } // TODO: $set_once userProperties?.let { @@ -146,7 +157,7 @@ public class PostHog private constructor() { } config?.optOut = false - config?.preferences?.setValue("opt-out", false) + config?.cachePreferences?.setValue("opt-out", false) } public fun optOut() { @@ -155,7 +166,7 @@ public class PostHog private constructor() { } config?.optOut = true - config?.preferences?.setValue("opt-out", true) + config?.cachePreferences?.setValue("opt-out", true) } // public fun register(key: String, value: Any) { @@ -228,6 +239,16 @@ public class PostHog private constructor() { props["\$group_set"] = it } + config?.memoryPreferences?.let { preferences -> + val groups = preferences.getValue("\$groups") as? Map + val newGroups = mutableMapOf() + groups?.let { + newGroups.putAll(it) + } + newGroups[type] = key + preferences.setValue("\$groups", newGroups) + } + capture("\$groupidentify", properties = props) } @@ -243,7 +264,9 @@ public class PostHog private constructor() { props["\$anon_distinct_id"] = anonymousId props["distinct_id"] = distinctId - featureFlags?.loadFeatureFlags(buildProperties(distinctId, props, null, null)) + val groups = config?.memoryPreferences?.getValue("\$groups") as? Map + + featureFlags?.loadFeatureFlags(buildProperties(distinctId, props, null, groups)) } public fun isFeatureEnabled(key: String, defaultValue: Boolean = false): Boolean { @@ -292,7 +315,11 @@ public class PostHog private constructor() { if (!isEnabled()) { return } - config?.preferences?.clear() + + // only remove properties, preserve BUILD and VERSION keys in order to to fix over-sending + // of 'Application Installed' events and under-sending of 'Application Updated' events + config?.cachePreferences?.clear(listOf("build", "build")) + config?.memoryPreferences?.clear(listOf()) queue?.clear() } @@ -377,6 +404,10 @@ public class PostHog private constructor() { shared.optOut() } + public fun group(type: String, key: String, groupProperties: Map? = null) { + shared.group(type, key, groupProperties = groupProperties) + } // TODO: add other methods } } +// TODO: $enabled_feature_flags? diff --git a/posthog-v3/posthog/src/main/java/com/posthog/PostHogConfig.kt b/posthog-v3/posthog/src/main/java/com/posthog/PostHogConfig.kt index 44db9513..aafa3e08 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/PostHogConfig.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/PostHogConfig.kt @@ -1,5 +1,6 @@ package com.posthog +import com.posthog.internal.PostHogMemoryPreferences import com.posthog.internal.PostHogNetworkStatus public class PostHogConfig( @@ -44,7 +45,9 @@ public class PostHogConfig( public var storagePrefix: String? = null @PostHogInternal - public var preferences: PostHogPreferences? = null + public var cachePreferences: PostHogPreferences? = null + + internal var memoryPreferences: PostHogPreferences = PostHogMemoryPreferences() @PostHogInternal public var networkStatus: PostHogNetworkStatus? = null diff --git a/posthog-v3/posthog/src/main/java/com/posthog/PostHogEvent.kt b/posthog-v3/posthog/src/main/java/com/posthog/PostHogEvent.kt index 4373b4d4..8571435b 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/PostHogEvent.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/PostHogEvent.kt @@ -6,7 +6,7 @@ import java.util.UUID public data class PostHogEvent( val event: String, - @SerializedName("distinct_id") // its now called $distinct_id but we need to keep compatibility + @SerializedName("distinct_id") val distinctId: String, val properties: Map? = null, val timestamp: Date = Date(), diff --git a/posthog-v3/posthog/src/main/java/com/posthog/PostHogPreferences.kt b/posthog-v3/posthog/src/main/java/com/posthog/PostHogPreferences.kt index c6a548c3..84c7cbbd 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/PostHogPreferences.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/PostHogPreferences.kt @@ -5,5 +5,5 @@ public interface PostHogPreferences { public fun setValue(key: String, value: Any) - public fun clear() + public fun clear(except: List) } diff --git a/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt b/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt index d91894f1..b436ebd2 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt @@ -88,4 +88,10 @@ internal class PostHogFeatureFlags(private val config: PostHogConfig, private va fun getFeatureFlagPayload(key: String, defaultValue: Any?): Any? { return readFeatureFlag(key, defaultValue, featureFlagPayloads) } + + fun getFeatureFlags(): Map? { + synchronized(featureFlagsLock) { + return featureFlags + } + } } diff --git a/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogMemoryPreferences.kt b/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogMemoryPreferences.kt index 066f4a23..c4cb6e81 100644 --- a/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogMemoryPreferences.kt +++ b/posthog-v3/posthog/src/main/java/com/posthog/internal/PostHogMemoryPreferences.kt @@ -3,17 +3,30 @@ package com.posthog.internal import com.posthog.PostHogPreferences internal class PostHogMemoryPreferences : PostHogPreferences { + private val lock = Any() private val preferences = mutableMapOf() override fun getValue(key: String, defaultValue: Any?): Any? { - return preferences[key] ?: defaultValue + synchronized(lock) { + return preferences[key] ?: defaultValue + } } override fun setValue(key: String, value: Any) { - preferences[key] = value + synchronized(lock) { + preferences[key] = value + } } - override fun clear() { - preferences.clear() + override fun clear(except: List) { + synchronized(lock) { + val it = preferences.iterator() + while (it.hasNext()) { + val entry = it.next() + if (!except.contains(entry.key)) { + it.remove() + } + } + } } }