Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions posthog-android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Next

- add param sendFeatureFlagEvent in function getFeatureFlag() for override config's
sendFeatureFlagEvent ([#319](https://github.com/PostHog/posthog-android/pull/319))

## 3.26.0 - 2025-11-05

- feat: Cache properties for flag evaluation ([#315](https://github.com/PostHog/posthog-android/pull/315))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ public class PostHogFake : PostHogInterface {
override fun isFeatureEnabled(
key: String,
defaultValue: Boolean,
sendFeatureFlagEvent: Boolean?,
): Boolean {
return false
}

override fun getFeatureFlag(
key: String,
defaultValue: Any?,
sendFeatureFlagEvent: Boolean?,
): Any? {
return null
}
Expand Down
120 changes: 83 additions & 37 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ public class PostHog private constructor(
config.logger.log("Setup called despite already being setup!")
return
}
config.logger = if (config.logger is PostHogNoOpLogger) PostHogPrintLogger(config) else config.logger
config.logger =
if (config.logger is PostHogNoOpLogger) PostHogPrintLogger(config) else config.logger

if (!apiKeys.add(config.apiKey)) {
config.logger.log("API Key: ${config.apiKey} already has a PostHog instance.")
Expand All @@ -92,8 +93,22 @@ public class PostHog private constructor(
val cachePreferences = config.cachePreferences ?: memoryPreferences
config.cachePreferences = cachePreferences
val api = PostHogApi(config)
val queue = config.queueProvider(config, api, PostHogApiEndpoint.BATCH, config.storagePrefix, queueExecutor)
val replayQueue = config.queueProvider(config, api, PostHogApiEndpoint.SNAPSHOT, config.replayStoragePrefix, replayExecutor)
val queue =
config.queueProvider(
config,
api,
PostHogApiEndpoint.BATCH,
config.storagePrefix,
queueExecutor,
)
val replayQueue =
config.queueProvider(
config,
api,
PostHogApiEndpoint.SNAPSHOT,
config.replayStoragePrefix,
replayExecutor,
)
val featureFlags =
config.remoteConfigProvider(config, api, remoteConfigExecutor) {
getDefaultPersonProperties()
Expand Down Expand Up @@ -178,7 +193,12 @@ public class PostHog private constructor(
// only because of testing in isolation, this flag is always enabled
if (reloadFeatureFlags) {
when {
config.remoteConfig -> loadRemoteConfigRequest(internalOnFeatureFlagsLoaded, config.onFeatureFlags)
config.remoteConfig ->
loadRemoteConfigRequest(
internalOnFeatureFlagsLoaded,
config.onFeatureFlags,
)

config.preloadFeatureFlags -> reloadFeatureFlags(config.onFeatureFlags)
}
}
Expand Down Expand Up @@ -730,8 +750,9 @@ public class PostHog private constructor(
get() {
synchronized(personProcessingLock) {
if (!isPersonProcessingLoaded) {
isPersonProcessingEnabled = getPreferences().getValue(PERSON_PROCESSING) as? Boolean
?: false
isPersonProcessingEnabled =
getPreferences().getValue(PERSON_PROCESSING) as? Boolean
?: false
isPersonProcessingLoaded = true
}
}
Expand Down Expand Up @@ -804,7 +825,10 @@ public class PostHog private constructor(
if (!isEnabled()) {
return
}
loadFeatureFlagsRequest(internalOnFeatureFlags = internalOnFeatureFlagsLoaded, onFeatureFlags = onFeatureFlags)
loadFeatureFlagsRequest(
internalOnFeatureFlags = internalOnFeatureFlagsLoaded,
onFeatureFlags = onFeatureFlags,
)
}

private fun loadFeatureFlagsRequest(
Expand Down Expand Up @@ -849,14 +873,21 @@ public class PostHog private constructor(
anonymousId = this.anonymousId
}

remoteConfig?.loadRemoteConfig(distinctId, anonymousId = anonymousId, groups, internalOnFeatureFlags, onFeatureFlags)
remoteConfig?.loadRemoteConfig(
distinctId,
anonymousId = anonymousId,
groups,
internalOnFeatureFlags,
onFeatureFlags,
)
}

public override fun isFeatureEnabled(
key: String,
defaultValue: Boolean,
sendFeatureFlagEvent: Boolean?,
): Boolean {
val value = getFeatureFlag(key, defaultValue)
val value = getFeatureFlag(key, defaultValue, sendFeatureFlagEvent)

if (value is Boolean) {
return value
Expand All @@ -872,49 +903,57 @@ public class PostHog private constructor(
private fun sendFeatureFlagCalled(
key: String,
value: Any?,
sendFeatureFlagEvent: Boolean?,
) {
var shouldSendFeatureFlagEvent = true
synchronized(featureFlagsCalledLock) {
val values = featureFlagsCalled[key] ?: mutableListOf()
if (values.contains(value)) {
shouldSendFeatureFlagEvent = false
} else {
values.add(value)
featureFlagsCalled[key] = values
val effectiveSendFeatureFlagEvent =
sendFeatureFlagEvent
?: config?.sendFeatureFlagEvent
?: false

if (effectiveSendFeatureFlagEvent) {
var shouldSendFeatureFlagEvent = true
synchronized(featureFlagsCalledLock) {
val values = featureFlagsCalled[key] ?: mutableListOf()
if (values.contains(value)) {
shouldSendFeatureFlagEvent = false
} else {
values.add(value)
featureFlagsCalled[key] = values
}
}
}

if (config?.sendFeatureFlagEvent == true && shouldSendFeatureFlagEvent) {
remoteConfig?.let {
val flagDetails = it.getFlagDetails(key)
val requestId = it.getRequestId()

val props = mutableMapOf<String, Any>()
props["\$feature_flag"] = key
// value should never be nullabe anyway
props["\$feature_flag_response"] = value ?: ""
props["\$feature_flag_request_id"] = requestId ?: ""
flagDetails?.let {
props["\$feature_flag_id"] = it.metadata.id
props["\$feature_flag_version"] = it.metadata.version
props["\$feature_flag_reason"] = it.reason?.description ?: ""
if (shouldSendFeatureFlagEvent) {
remoteConfig?.let {
val flagDetails = it.getFlagDetails(key)
val requestId = it.getRequestId()

val props = mutableMapOf<String, Any>()
props["\$feature_flag"] = key
// value should never be nullabe anyway
props["\$feature_flag_response"] = value ?: ""
props["\$feature_flag_request_id"] = requestId ?: ""
flagDetails?.let {
props["\$feature_flag_id"] = it.metadata.id
props["\$feature_flag_version"] = it.metadata.version
props["\$feature_flag_reason"] = it.reason?.description ?: ""
}
capture(PostHogEventName.FEATURE_FLAG_CALLED.event, properties = props)
}
capture("\$feature_flag_called", properties = props)
}
}
}

public override fun getFeatureFlag(
key: String,
defaultValue: Any?,
sendFeatureFlagEvent: Boolean?,
): Any? {
if (!isEnabled()) {
return defaultValue
}
val value = remoteConfig?.getFeatureFlag(key, defaultValue) ?: defaultValue

sendFeatureFlagCalled(key, value)

sendFeatureFlagCalled(key, value, sendFeatureFlagEvent)
return value
}

Expand Down Expand Up @@ -1258,12 +1297,19 @@ public class PostHog private constructor(
public override fun isFeatureEnabled(
key: String,
defaultValue: Boolean,
): Boolean = shared.isFeatureEnabled(key, defaultValue = defaultValue)
sendFeatureFlagEvent: Boolean?,
): Boolean =
shared.isFeatureEnabled(
key,
defaultValue = defaultValue,
sendFeatureFlagEvent = sendFeatureFlagEvent,
)

public override fun getFeatureFlag(
key: String,
defaultValue: Any?,
): Any? = shared.getFeatureFlag(key, defaultValue = defaultValue)
sendFeatureFlagEvent: Boolean?,
): Any? = shared.getFeatureFlag(key, defaultValue = defaultValue, sendFeatureFlagEvent)

public override fun getFeatureFlagPayload(
key: String,
Expand Down
4 changes: 4 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ public interface PostHogInterface : PostHogCoreInterface {
* Docs https://posthog.com/docs/feature-flags and https://posthog.com/docs/experiments
* @param key the Key
* @param defaultValue the default value if not found, false if not given
* @param sendFeatureFlagEvent (optional) If false, we won't send an $feature_flag_call event to PostHog.
*/
public fun isFeatureEnabled(
key: String,
defaultValue: Boolean = false,
sendFeatureFlagEvent: Boolean? = null,
): Boolean

/**
* Returns the feature flag
* Docs https://posthog.com/docs/feature-flags and https://posthog.com/docs/experiments
* @param key the Key
* @param defaultValue the default value if not found
* @param sendFeatureFlagEvent (optional) If false, we won't send an $feature_flag_call event to PostHog.
*/
public fun getFeatureFlag(
key: String,
defaultValue: Any? = null,
sendFeatureFlagEvent: Boolean? = null,
): Any?

/**
Expand Down
2 changes: 1 addition & 1 deletion posthog/src/main/java/com/posthog/PostHogStateless.kt
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ public open class PostHogStateless protected constructor(
// value should never be nullable anyway
props["\$feature_flag_response"] = value ?: ""

captureStateless("\$feature_flag_called", distinctId, properties = props)
captureStateless(PostHogEventName.FEATURE_FLAG_CALLED.event, distinctId, properties = props)
}
}
}
Expand Down
Loading