From 4ef15d0b8e35d97d67de918a9ce01f48a620a716 Mon Sep 17 00:00:00 2001 From: Mohit Mandalia Date: Sat, 21 Feb 2026 07:49:02 +0530 Subject: [PATCH 1/5] feat: start and stop integrations on opt out --- posthog/src/main/java/com/posthog/PostHog.kt | 111 ++++++++++++------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 687ee932..7a7b8e66 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -179,32 +179,9 @@ public class PostHog private constructor( startSession() - config.integrations.forEach { - try { - it.install(this) - - if (it is PostHogSessionReplayHandler) { - sessionReplayHandler = it - - // resume because we just created the session id above with - // the startSession call - if (isSessionReplayConfigEnabled()) { - startSessionReplay(resumeCurrent = true) - } - } else if (it is PostHogSurveysHandler) { - // surveys integration so we can notify it about captured events - surveysHandler = it - // Immediately push any cached surveys from remote config - try { - val surveys = remoteConfig?.getSurveys() ?: emptyList() - it.onSurveysLoaded(surveys) - } catch (e: Throwable) { - config.logger.log("Pushing cached surveys to integration failed: $e.") - } - } - } catch (e: Throwable) { - config.logger.log("Integration ${it.javaClass.name} failed to install: $e.") - } + if(!config.optOut) { + // should not install integrations if in opt-out state + installIntegrations(config.integrations) } // only because of testing in isolation, this flag is always enabled @@ -225,6 +202,36 @@ public class PostHog private constructor( } } + private fun installIntegrations(integrations: List) { + integrations.forEach { + try { + it.install(this) + + if (it is PostHogSessionReplayHandler) { + sessionReplayHandler = it + + // resume because we just created the session id above with + // the startSession call + if (isSessionReplayConfigEnabled()) { + startSessionReplay(resumeCurrent = true) + } + } else if (it is PostHogSurveysHandler) { + // surveys integration so we can notify it about captured events + surveysHandler = it + // Immediately push any cached surveys from remote config + try { + val surveys = remoteConfig?.getSurveys() ?: emptyList() + it.onSurveysLoaded(surveys) + } catch (e: Throwable) { + config?.logger?.log("Pushing cached surveys to integration failed: $e.") + } + } + } catch (e: Throwable) { + config?.logger?.log("Integration ${it.javaClass.name} failed to install: $e.") + } + } + } + private fun notifyIntegrationsRemoteConfig(config: PostHogConfig) { config.integrations.forEach { integration -> try { @@ -273,20 +280,7 @@ public class PostHog private constructor( config?.let { config -> apiKeys.remove(config.apiKey) - config.integrations.forEach { - try { - it.uninstall() - - if (it is PostHogSessionReplayHandler) { - sessionReplayHandler = null - } else if (it is PostHogSurveysHandler) { - surveysHandler = null - } - } catch (e: Throwable) { - config.logger - .log("Integration ${it.javaClass.name} failed to uninstall: $e.") - } - } + uninstallIntegrations(config.integrations) } queue?.stop() @@ -301,6 +295,23 @@ public class PostHog private constructor( } } + private fun uninstallIntegrations(integrations: List) { + integrations.forEach { + try { + it.uninstall() + + if (it is PostHogSessionReplayHandler) { + sessionReplayHandler = null + } else if (it is PostHogSurveysHandler) { + surveysHandler = null + } + } catch (e: Throwable) { + config?.logger + ?.log("Integration ${it.javaClass.name} failed to uninstall: $e.") + } + } + } + private var anonymousId: String get() { var anonymousId: String? @@ -571,10 +582,20 @@ public class PostHog private constructor( return } + if (!isOptOut()) { + return + } + synchronized(optOutLock) { config?.optOut = false getPreferences().setValue(OPT_OUT, false) } + + synchronized(setupLock) { + config?.integrations?.let { + installIntegrations(it) + } + } } public override fun optOut() { @@ -582,10 +603,20 @@ public class PostHog private constructor( return } + if (isOptOut()) { + return + } + synchronized(optOutLock) { config?.optOut = true getPreferences().setValue(OPT_OUT, true) } + + synchronized(setupLock) { + config?.integrations?.let { + uninstallIntegrations(it) + } + } } /** From 7ab2e63ed25ebc9169c0e63641e3073751d7b8e3 Mon Sep 17 00:00:00 2001 From: Mohit Mandalia Date: Sat, 21 Feb 2026 19:15:50 +0530 Subject: [PATCH 2/5] Add tests --- .../src/test/java/com/posthog/PostHogTest.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/posthog/src/test/java/com/posthog/PostHogTest.kt b/posthog/src/test/java/com/posthog/PostHogTest.kt index edecd437..7c40377d 100644 --- a/posthog/src/test/java/com/posthog/PostHogTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogTest.kt @@ -151,6 +151,24 @@ internal class PostHogTest { sut.close() } + @Test + fun `do not install integrations on setup as state is opt-out then opt-in and install`() { + val http = mockHttp() + val url = http.url("/") + + val integration = FakePostHogIntegration() + + val sut = getSut(url.toString(), integration = integration, optOut = true) + + assertFalse(integration.installed) + + sut.optIn() + + assertTrue(integration.installed) + + sut.close() + } + @Test fun `uninstall integrations`() { val http = mockHttp() @@ -165,6 +183,20 @@ internal class PostHogTest { assertFalse(integration.installed) } + @Test + fun `uninstall integrations on opt-out`() { + val http = mockHttp() + val url = http.url("/") + + val integration = FakePostHogIntegration() + + val sut = getSut(url.toString(), integration = integration) + + sut.optOut() + + assertFalse(integration.installed) + } + @Test fun `setup sets in memory cached preferences if not given`() { val http = mockHttp() From aed72c6a55e823145d43bc3d2b7579ebebf74222 Mon Sep 17 00:00:00 2001 From: Mohit Mandalia Date: Sat, 21 Feb 2026 19:16:08 +0530 Subject: [PATCH 3/5] Track installed integrations --- posthog/src/main/java/com/posthog/PostHog.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 7a7b8e66..355f42c2 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -86,6 +86,8 @@ public class PostHog private constructor( } } + private val installedIntegrations = mutableListOf() + public override fun setup(config: T) { synchronized(setupLock) { try { @@ -203,9 +205,16 @@ public class PostHog private constructor( } private fun installIntegrations(integrations: List) { + if (!installedIntegrations.isEmpty()) { + config?.logger?.log("Integrations already installed. Call uninstallIntegrations() first") + return + } + + val installed = mutableListOf() integrations.forEach { try { it.install(this) + installed.add(it) if (it is PostHogSessionReplayHandler) { sessionReplayHandler = it @@ -230,6 +239,8 @@ public class PostHog private constructor( config?.logger?.log("Integration ${it.javaClass.name} failed to install: $e.") } } + + installedIntegrations.addAll(installed) } private fun notifyIntegrationsRemoteConfig(config: PostHogConfig) { @@ -299,6 +310,7 @@ public class PostHog private constructor( integrations.forEach { try { it.uninstall() + installedIntegrations.remove(it) if (it is PostHogSessionReplayHandler) { sessionReplayHandler = null From de25339fc1b1a34278c1b1600aada85e7c3963ff Mon Sep 17 00:00:00 2001 From: Mohit Mandalia Date: Sat, 21 Feb 2026 19:18:00 +0530 Subject: [PATCH 4/5] spotless --- posthog/src/main/java/com/posthog/PostHog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 355f42c2..441e476a 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -181,7 +181,7 @@ public class PostHog private constructor( startSession() - if(!config.optOut) { + if (!config.optOut) { // should not install integrations if in opt-out state installIntegrations(config.integrations) } @@ -307,7 +307,7 @@ public class PostHog private constructor( } private fun uninstallIntegrations(integrations: List) { - integrations.forEach { + integrations.forEach { try { it.uninstall() installedIntegrations.remove(it) From ccdfabe20ab34f05f8092974769253be710a6faa Mon Sep 17 00:00:00 2001 From: Mohit Mandalia Date: Sat, 21 Feb 2026 19:51:40 +0530 Subject: [PATCH 5/5] update changelog --- posthog/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/posthog/CHANGELOG.md b/posthog/CHANGELOG.md index edf93b2c..3c1bf03a 100644 --- a/posthog/CHANGELOG.md +++ b/posthog/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- feat: start/stop integrations when calling `optIn()` or `optOut()`. ([#438](https://github.com/PostHog/posthog-android/pull/438)) + ## 6.6.2 ### Patch Changes