diff --git a/CHANGELOG.md b/CHANGELOG.md index 4680736f..78ebb755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- fix: clear feature flags cache when flags were cleared up server side ([#246](https://github.com/PostHog/posthog-android/pull/246)) + ## 3.15.0 - 2025-05-14 - feat: log request and payload requests if debug is enabled ([#249](https://github.com/PostHog/posthog-android/pull/249)) diff --git a/posthog-android/lint-baseline.xml b/posthog-android/lint-baseline.xml index f284f731..e49a6346 100644 --- a/posthog-android/lint-baseline.xml +++ b/posthog-android/lint-baseline.xml @@ -36,7 +36,7 @@ - preferences.remove(FLAGS) - preferences.remove(FEATURE_FLAGS) - preferences.remove(FEATURE_FLAGS_PAYLOAD) - } + clearFlags() return@let } @@ -325,7 +336,6 @@ internal class PostHogRemoteConfig( mapOf(), ) as? Map ?: mapOf() - @Suppress("UNCHECKED_CAST") val cachedRequestId = preferences.getValue(FEATURE_FLAG_REQUEST_ID) as? String synchronized(featureFlagsLock) { @@ -432,38 +442,43 @@ internal class PostHogRemoteConfig( fun isSessionReplayFlagActive(): Boolean = sessionReplayFlagActive fun getRequestId(): String? { - if (!isFeatureFlagsLoaded) { - loadFeatureFlagsFromCache() - } + loadFeatureFlagsFromCacheIfNeeded() synchronized(featureFlagsLock) { return requestId } } fun getFlagDetails(key: String): FeatureFlag? { - if (!isFeatureFlagsLoaded) { - loadFeatureFlagsFromCache() - } + loadFeatureFlagsFromCacheIfNeeded() synchronized(featureFlagsLock) { return flags?.get(key) as? FeatureFlag } } + private fun clearFlags() { + // call this method after synchronized(featureFlagsLock) + this.featureFlags = null + this.featureFlagPayloads = null + this.flags = null + this.requestId = null + + config.cachePreferences?.let { preferences -> + preferences.remove(FLAGS) + preferences.remove(FEATURE_FLAGS) + preferences.remove(FEATURE_FLAGS_PAYLOAD) + preferences.remove(FEATURE_FLAG_REQUEST_ID) + } + } + fun clear() { synchronized(featureFlagsLock) { - featureFlags = null - featureFlagPayloads = null sessionReplayFlagActive = false isFeatureFlagsLoaded = false - requestId = null - config.cachePreferences?.let { preferences -> - preferences.remove(FEATURE_FLAGS) - preferences.remove(FEATURE_FLAGS_PAYLOAD) - preferences.remove(SESSION_REPLAY) - preferences.remove(FEATURE_FLAG_REQUEST_ID) - } + clearFlags() + + config.cachePreferences?.remove(SESSION_REPLAY) } synchronized(remoteConfigLock) { diff --git a/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt b/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt index c39ade10..ba475255 100644 --- a/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt +++ b/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt @@ -342,6 +342,45 @@ internal class PostHogFeatureFlagsTest { assertNull(preferences.getValue(FEATURE_FLAGS)) } + @Test + fun `clear cached flags when there are no no server side flags`() { + val http = + mockHttp( + response = + MockResponse().setBody( + """ + { + "featureFlags": {"flag1": true}, + "featureFlagPayloads": {"flag1": "payload1"} + } + """.trimIndent(), + ), + ) + + val file = File("src/test/resources/json/basic-remote-config-no-flags.json") + val responseText = file.readText() + + http.enqueue(MockResponse().setBody(responseText)) + val url = http.url("/") + + val sut = getSut(host = url.toString()) + + // Load initial flags + sut.loadFeatureFlags("test_id", null, null) + executor.awaitExecution() + + assertNotNull(preferences.getValue(FEATURE_FLAGS)) + assertTrue(sut.getFeatureFlag("flag1", defaultValue = false) as Boolean) + + // Load initial flags + sut.loadRemoteConfig("my_identify", anonymousId = "anonId", emptyMap(), null) + + executor.shutdownAndAwaitTermination() + + assertNull(preferences.getValue(FEATURE_FLAGS)) + assertFalse(sut.getFeatureFlag("flag1", defaultValue = false) as Boolean) + } + @Test fun `do not preload flags if distinct id is blank`() { val file = File("src/test/resources/json/basic-remote-config.json")