Skip to content
Merged
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
1 change: 1 addition & 0 deletions .changeset/android-v4-major.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
- **Breaking:** remove the deprecated `PostHogSessionReplayConfig.debouncerDelayMs`; use `throttleDelayMs` instead
- **Breaking:** return `null` instead of the raw string for a malformed, empty or whitespace-only feature flag payload from `getFeatureFlagResult(key)?.payload`; payloads must be valid JSON (a JSON `null` payload now also reads as `null` instead of the string `"null"`)
- **Breaking:** make SDK-internal helpers in the `com.posthog.internal` and `com.posthog.android.replay.internal` packages non-public
- **Breaking:** remove the deprecated `remoteConfig` config option; the SDK now always loads remote config at setup
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ internal class PostHogSurveyHostTest {
PostHogConfig("host-reset", "http://127.0.0.1:1").apply {
cachePreferences = PostHogMemoryPreferences()
preloadFeatureFlags = false
remoteConfig = false
reuseAnonymousId = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3054,15 +3054,9 @@ public class PostHogReplayIntegration(
}

// Whether to buffer until the live remote config resolves: only when a remote config holder
// exists and hasn't resolved yet, AND a fetch will actually be attempted at setup. If neither
// remoteConfig nor preloadFeatureFlags is enabled, PostHog.setup dispatches no /config or /flags
// request, so no onRemoteConfig callback ever arrives to disarm the gate —
// buffering would then grow unbounded for the whole session. Don't arm in that case.
// exists and hasn't resolved yet. PostHog.setup always requests /config, so the
// onRemoteConfig callback disarms the gate.
private fun shouldAwaitFirstRemoteConfig(): Boolean {
@Suppress("DEPRECATION")
if (!config.remoteConfig && !config.preloadFeatureFlags) {
return false
}
return config.remoteConfigHolder?.let { !it.hasRemoteConfigFetched() } ?: false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ internal class PostHogAndroidAppVersionTest {
events: MutableList<PostHogEvent>,
configure: PostHogAndroidConfig.() -> Unit = {},
): PostHogAndroidConfig =
PostHogAndroidConfig(projectToken).apply {
PostHogAndroidConfig(projectToken, "http://127.0.0.1:1").apply {
cachePreferences = preferences
captureApplicationLifecycleEvents = captureLifecycle
remoteConfig = false
preloadFeatureFlags = false
configure()
addBeforeSend {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ internal class PostHogAndroidEventSnapshotsTest {
sut.captureException(fixedThrowable(), mapOf("handled" to true, "component" to "checkout"))
sut.flush()

val request = fixture.server.takeRequests("/flags/?v=2", "/batch").single { it.path == "/batch" }
val request = fixture.server.takeRequests(CONFIG_PATH, "/flags/?v=2", "/batch").single { it.path == "/batch" }
val snapshot = requestSnapshot(request, fixture.config)
normalizeBatchVolatileFields(snapshot)

Expand All @@ -118,7 +118,7 @@ internal class PostHogAndroidEventSnapshotsTest {
fixture.client.reloadFeatureFlags(PostHogOnFeatureFlags { loaded.countDown() })

assertTrue(loaded.await(10, TimeUnit.SECONDS), "Feature flags request did not finish")
val request = fixture.server.takeRequests("/flags/?v=2").single()
val request = fixture.server.takeRequests(CONFIG_PATH, "/flags/?v=2").single { it.path == "/flags/?v=2" }

assertSnapshot("flags-request.json", requestSnapshot(request, fixture.config), fixture.config)
}
Expand Down Expand Up @@ -152,7 +152,7 @@ internal class PostHogAndroidEventSnapshotsTest {
)
fixture.client.flush()

val request = fixture.server.takeRequests("/s/").single()
val request = fixture.server.takeRequests(CONFIG_PATH, "/s/").single { it.path == "/s/" }
val snapshot = requestSnapshot(request, fixture.config)
normalizeReplayVolatileFields(snapshot)

Expand All @@ -163,6 +163,8 @@ internal class PostHogAndroidEventSnapshotsTest {
mockContextAppStart(context, tmpDir)
val server =
MockWebServer().apply {
// setup always requests /config first
enqueue(MockResponse().setBody(CONFIG_RESPONSE).setHeader("Content-Type", "application/json"))
repeat(10) {
enqueue(MockResponse().setBody(FLAGS_RESPONSE).setHeader("Content-Type", "application/json"))
}
Expand All @@ -185,8 +187,6 @@ internal class PostHogAndroidEventSnapshotsTest {
flushIntervalSeconds = 60
maxBatchSize = 20
preloadFeatureFlags = false
@Suppress("DEPRECATION")
remoteConfig = false
captureApplicationLifecycleEvents = false
captureDeepLinks = false
captureScreenViews = false
Expand Down Expand Up @@ -412,6 +412,8 @@ internal class PostHogAndroidEventSnapshotsTest {
private val FIXED_DATE = Date(FIXED_MILLIS)
private val SESSION_ID = UUID.fromString("018bcfe5-687b-7abc-8def-0123456789ab")
private const val FIXED_TIMESTAMP = "2023-11-14T22:13:20.123Z"
private const val CONFIG_PATH = "/array/$PROJECT_TOKEN/config"
private const val CONFIG_RESPONSE = """{"hasFeatureFlags":true}"""
private const val FLAGS_RESPONSE =
"""{
"flags": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,14 @@ internal class InteractionActivityTest {
fun `real Compose and embedded Views capture through window with replay off`() {
val events = CopyOnWriteArrayList<PostHogEvent>()
val config =
PostHogAndroidConfig("test").apply {
PostHogAndroidConfig("test", "http://127.0.0.1:1").apply {
captureElementInteractions = true
sessionReplay = false
captureApplicationLifecycleEvents = false
captureScreenViews = false
captureDeepLinks = false
capturePushNotificationOpened = false
capturePushNotificationSubscriptions = false
@Suppress("DEPRECATION") // Test isolation: do not fetch remote configuration.
remoteConfig = false
preloadFeatureFlags = false
addBeforeSend { event ->
events += event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal class InteractionDetectorActivityTest {
@Suppress("DEPRECATION")
private fun setup() {
val config =
PostHogAndroidConfig("test").apply {
PostHogAndroidConfig("test", "http://127.0.0.1:1").apply {
captureRageClicks = true
captureDeadClicks = true
sessionReplay = false
Expand All @@ -62,7 +62,6 @@ internal class InteractionDetectorActivityTest {
captureDeepLinks = false
capturePushNotificationOpened = false
capturePushNotificationSubscriptions = false
remoteConfig = false
preloadFeatureFlags = false
addBeforeSend { event ->
events += event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ internal class InteractionDetectorIntegrationTest {

@Suppress("DEPRECATION")
private val config =
PostHogAndroidConfig(PROJECT_TOKEN).apply {
PostHogAndroidConfig(PROJECT_TOKEN, "http://127.0.0.1:1").apply {
captureRageClicks = true
captureDeadClicks = true
sessionReplay = false
cachePreferences = PostHogMemoryPreferences()
remoteConfig = false
preloadFeatureFlags = false
addBeforeSend { event ->
if (event.event in listOf("\$rageclick", "\$dead_click", "\$autocapture")) events += event
Expand Down Expand Up @@ -253,9 +252,8 @@ internal class InteractionDetectorIntegrationTest {
fun `secondary session changes use normal capture context when owner is not notified`() {
val secondary =
PostHog.with(
PostHogAndroidConfig("secondary-test-key").apply {
PostHogAndroidConfig("secondary-test-key", "http://127.0.0.1:1").apply {
cachePreferences = PostHogMemoryPreferences()
remoteConfig = false
preloadFeatureFlags = false
addBeforeSend { null }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2127,53 +2127,6 @@ internal class PostHogReplayIntegrationTest {
}
}

@Test
fun `gate does not arm when neither remoteConfig nor preloadFeatureFlags will fetch`() {
// With both remoteConfig and preloadFeatureFlags disabled, setup dispatches no /config or
// /flags request, so no onRemoteConfig callback arrives to disarm the gate. Arming it would
// buffer every snapshot for the whole session; instead it must stay disarmed and persist.
val remoteConfigMock =
mock<PostHogRemoteConfig> {
on { isSessionReplayFlagActive() } doReturn true
on { hasRemoteConfigFetched() } doReturn false
on { makeSamplingDecision(any()) } doReturn true
on { getEventTriggers() } doReturn emptySet<String>()
on { getRecordingMinimumDurationMs() } doReturn null
}
val config =
PostHogAndroidConfig(PROJECT_TOKEN).apply {
remoteConfigHolder = remoteConfigMock
sessionReplay = true
@Suppress("DEPRECATION")
remoteConfig = false
preloadFeatureFlags = false
}
val storagePrefix = tmpDir.newFolder().absolutePath
val executor = createReplayExecutor()
val innerQueue =
PostHogQueue(
config,
EndpointSpec.snapshot(config, PostHogApi(config), storagePrefix),
executor,
)
val replayQueue = PostHogReplayQueue(config, innerQueue, storagePrefix, executor)
config.replayQueueHolder = replayQueue
val sut = PostHogReplayIntegration(mock<Context>(), config, MainHandler())
val postHog = mock<PostHogInterface>()
whenever(postHog.getSessionId()).thenReturn(UUID.randomUUID())
sut.install(postHog)
sut.start(resumeCurrent = true)
try {
// No remote-config callback will ever fire, so buffering must not be armed.
replayQueue.add(createTestEvent("snapshot_1"))
awaitCondition { replayQueue.depth == 1 }
assertEquals(0, replayQueue.bufferDepth)
assertEquals(1, replayQueue.depth)
} finally {
sut.uninstall()
}
}

@Test
fun `mid-session resume forces a fresh keyframe by clearing per-view snapshot state`() {
// A mid-session flag-on resume must emit a fresh full snapshot: while stopped, per-view state
Expand Down Expand Up @@ -2599,9 +2552,7 @@ internal class PostHogReplayIntegrationTest {
@Test
@Config(sdk = [26], shadows = [SnapshotBoundaryShadowPixelCopy::class])
fun `committed snapshot keeps its session when rotation precedes core enrichment`() {
val config = PostHogAndroidConfig(PROJECT_TOKEN)
@Suppress("DEPRECATION")
config.remoteConfig = false
val config = PostHogAndroidConfig(PROJECT_TOKEN, "http://127.0.0.1:1")
config.preloadFeatureFlags = false
var captured: PostHogEvent? = null
config.addBeforeSend { event ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ internal class PostHogSurveysRestartTest {
PostHogAndroidConfig(projectToken),
)
preloadFeatureFlags = false
remoteConfig = false
surveys = true
surveysConfig.surveysDelegate = renderer
surveysConfig.overrideDisplayLanguage = language
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ public open class PostHogConfig constructor(
debug = debug,
sendFeatureFlagEvent = sendFeatureFlagEvent,
preloadFeatureFlags = preloadFeatureFlags,
remoteConfig = remoteConfig,
flushAt = flushAt,
maxQueueSize = maxQueueSize,
maxBatchSize = maxBatchSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ internal class PostHogConfigTest {
assertEquals(true, coreConfig.debug)
assertEquals(false, coreConfig.sendFeatureFlagEvent)
assertEquals(false, coreConfig.preloadFeatureFlags)
assertEquals(false, coreConfig.remoteConfig)
assertEquals(15, coreConfig.flushAt)
assertEquals(750, coreConfig.maxQueueSize)
assertEquals(30, coreConfig.maxBatchSize)
Expand Down
6 changes: 2 additions & 4 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ public class com/posthog/PostHogConfig {
public static final field DEFAULT_MAX_QUEUE_SIZE I
public static final field DEFAULT_US_ASSETS_HOST Ljava/lang/String;
public static final field DEFAULT_US_HOST Ljava/lang/String;
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZZIIIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lcom/posthog/logs/PostHogLogsConfig;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;Ljava/lang/String;Lcom/posthog/PostHogBootstrapConfig;Lkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZZIIIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lcom/posthog/logs/PostHogLogsConfig;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;Ljava/lang/String;Lcom/posthog/PostHogBootstrapConfig;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lcom/posthog/logs/PostHogLogsConfig;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;Ljava/lang/String;Lcom/posthog/PostHogBootstrapConfig;Lkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lcom/posthog/logs/PostHogLogsConfig;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;Ljava/lang/String;Lcom/posthog/PostHogBootstrapConfig;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addBeforeSend (Lcom/posthog/PostHogBeforeSend;)V
public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V
public final fun getBeforeSendList ()Ljava/util/List;
Expand Down Expand Up @@ -196,7 +196,6 @@ public class com/posthog/PostHogConfig {
public final fun getPushIdentityProvider ()Lkotlin/jvm/functions/Function3;
public final fun getQueueProvider ()Lkotlin/jvm/functions/Function5;
public final fun getReleaseIdentifier ()Ljava/lang/String;
public final fun getRemoteConfig ()Z
public final fun getRemoteConfigHolder ()Lcom/posthog/internal/PostHogRemoteConfig;
public final fun getRemoteConfigProvider ()Lkotlin/jvm/functions/Function6;
public final fun getReplayStoragePrefix ()Ljava/lang/String;
Expand Down Expand Up @@ -246,7 +245,6 @@ public class com/posthog/PostHogConfig {
public final fun setProxy (Ljava/net/Proxy;)V
public final fun setPushIdentityProvider (Lkotlin/jvm/functions/Function3;)V
public final fun setReleaseIdentifier (Ljava/lang/String;)V
public final fun setRemoteConfig (Z)V
public final fun setRemoteConfigHolder (Lcom/posthog/internal/PostHogRemoteConfig;)V
public final fun setReplayStoragePrefix (Ljava/lang/String;)V
public final fun setRequestHeaders (Ljava/util/Map;)V
Expand Down
14 changes: 4 additions & 10 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -393,23 +393,17 @@ public class PostHog private constructor(
}

// only because of testing in isolation, this flag is always enabled
@Suppress("DEPRECATION")
if (reloadFeatureFlags) {
// Bootstrap already seeded the flags in RemoteConfig's init; fire the flags-loaded
// callbacks now so listeners aren't blocked on the first network response.
if (remoteConfig?.hasBootstrapFlags() == true) {
notifyFeatureFlagsCallback(internalOnFeatureFlagsLoaded)
notifyFeatureFlagsCallback(config.onFeatureFlags)
}
when {
config.remoteConfig ->
loadRemoteConfigRequest(
internalOnFeatureFlagsLoaded,
config.onFeatureFlags,
)

config.preloadFeatureFlags -> reloadFeatureFlags(config.onFeatureFlags)
}
loadRemoteConfigRequest(
internalOnFeatureFlagsLoaded,
config.onFeatureFlags,
)
}

// Reconcile a differing identified bootstrap after the setup reload is dispatched.
Expand Down
9 changes: 0 additions & 9 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,6 @@ public open class PostHogConfig(
* Default: true
*/
public var setDefaultPersonProperties: Boolean = true,
/**
* Preload PostHog remote config automatically
* Defaults to true
*/
@Deprecated(
message = "Remote config is now always enabled. This option is a no-op and will be removed in a future version.",
level = DeprecationLevel.WARNING,
)
public var remoteConfig: Boolean = true,
/**
* Number of minimum events before they are sent over the wire
* Defaults to 20
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ internal class PostHogBeforeSendTest {
storagePrefix = tmpDir.newFolder().absolutePath
replayStoragePrefix = tmpDir.newFolder().absolutePath
preloadFeatureFlags = false
remoteConfig = false
cachePreferences = PostHogMemoryPreferences()
hooks.forEach { addBeforeSend(it) }
}
Expand Down
Loading
Loading