Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: session replay and auto capture works with 'with' method #217

Merged
merged 9 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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 CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next

- chore: Session Replay - GA
- fix: session replay and auto capture works with 'with' method ([#214](https://github.com/PostHog/posthog-flutter/pull/214))

## 3.10.0 - 2025-01-07

Expand Down
2 changes: 1 addition & 1 deletion posthog-android/api/posthog-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public final class com/posthog/android/replay/PostHogReplayIntegration : com/pos
public static final field ANDROID_COMPOSE_VIEW_CLASS_NAME Ljava/lang/String;
public static final field PH_NO_CAPTURE_LABEL Ljava/lang/String;
public fun <init> (Landroid/content/Context;Lcom/posthog/android/PostHogAndroidConfig;Lcom/posthog/android/internal/MainHandler;)V
public fun install ()V
public fun install (Lcom/posthog/PostHogInterface;)V
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically a breaking change but we don't document this integration API anywhere so its ok (we only use intergrations internally)

public fun uninstall ()V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import com.posthog.PostHog
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroidConfig

/**
Expand All @@ -17,6 +17,8 @@ internal class PostHogActivityLifecycleCallbackIntegration(
private val application: Application,
private val config: PostHogAndroidConfig,
) : ActivityLifecycleCallbacks, PostHogIntegration {
private var postHog: PostHogInterface? = null

override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?,
Expand All @@ -39,7 +41,7 @@ internal class PostHogActivityLifecycleCallbackIntegration(
} finally {
data?.let { props["url"] = it.toString() }
intent.getReferrerInfo(config).let { props.putAll(it) }
PostHog.capture("Deep Link Opened", properties = props)
postHog?.capture("Deep Link Opened", properties = props)
}
}
}
Expand All @@ -50,7 +52,7 @@ internal class PostHogActivityLifecycleCallbackIntegration(
val screenName = activity.activityLabelOrName(config)

if (!screenName.isNullOrEmpty()) {
PostHog.screen(screenName)
postHog?.screen(screenName)
}
}
}
Expand All @@ -73,11 +75,13 @@ internal class PostHogActivityLifecycleCallbackIntegration(
override fun onActivityDestroyed(activity: Activity) {
}

override fun install() {
override fun install(postHog: PostHogInterface) {
this.postHog = postHog
application.registerActivityLifecycleCallbacks(this)
}

override fun uninstall() {
this.postHog = null
application.unregisterActivityLifecycleCallbacks(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.posthog.android.internal

import android.content.Context
import com.posthog.PostHog
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroidConfig
import com.posthog.internal.PostHogPreferences.Companion.BUILD
import com.posthog.internal.PostHogPreferences.Companion.VERSION
Expand All @@ -16,7 +16,7 @@ internal class PostHogAppInstallIntegration(
private val context: Context,
private val config: PostHogAndroidConfig,
) : PostHogIntegration {
override fun install() {
override fun install(postHog: PostHogInterface) {
getPackageInfo(context, config)?.let { packageInfo ->
config.cachePreferences?.let { preferences ->
val versionName = packageInfo.versionName
Expand Down Expand Up @@ -52,7 +52,7 @@ internal class PostHogAppInstallIntegration(
preferences.setValue(VERSION, versionName)
preferences.setValue(BUILD, versionCode)

PostHog.capture(event, properties = props)
postHog.capture(event, properties = props)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.posthog.PostHog
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroidConfig
import java.util.Timer
import java.util.TimerTask
Expand All @@ -30,6 +30,8 @@ internal class PostHogLifecycleObserverIntegration(
private val lastUpdatedSession = AtomicLong(0L)
private val sessionMaxInterval = (1000 * 60 * 30).toLong() // 30 minutes

private var postHog: PostHogInterface? = null

private companion object {
// in case there are multiple instances or the SDK is closed/setup again
// the value is still cached
Expand All @@ -54,7 +56,7 @@ internal class PostHogLifecycleObserverIntegration(
fromBackground = true
}

PostHog.capture("Application Opened", properties = props)
postHog?.capture("Application Opened", properties = props)
}
}

Expand All @@ -67,7 +69,7 @@ internal class PostHogLifecycleObserverIntegration(
if (lastUpdatedSession == 0L ||
(lastUpdatedSession + sessionMaxInterval) <= currentTimeMillis
) {
PostHog.startSession()
postHog?.startSession()
}
this.lastUpdatedSession.set(currentTimeMillis)
}
Expand All @@ -85,7 +87,7 @@ internal class PostHogLifecycleObserverIntegration(
timerTask =
object : TimerTask() {
override fun run() {
PostHog.endSession()
postHog?.endSession()
}
}
timer.schedule(timerTask, sessionMaxInterval)
Expand All @@ -94,7 +96,7 @@ internal class PostHogLifecycleObserverIntegration(

override fun onStop(owner: LifecycleOwner) {
if (config.captureApplicationLifecycleEvents) {
PostHog.capture("Application Backgrounded")
postHog?.capture("Application Backgrounded")
}

val currentTimeMillis = config.dateProvider.currentTimeMillis()
Expand All @@ -106,7 +108,8 @@ internal class PostHogLifecycleObserverIntegration(
lifecycle.addObserver(this)
}

override fun install() {
override fun install(postHog: PostHogInterface) {
this.postHog = postHog
try {
if (isMainThread(mainHandler)) {
add()
Expand All @@ -126,6 +129,7 @@ internal class PostHogLifecycleObserverIntegration(

override fun uninstall() {
try {
this.postHog = null
if (isMainThread(mainHandler)) {
remove()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getAllSemanticsNodes
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.posthog.PostHog
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroidConfig
import com.posthog.android.internal.MainHandler
import com.posthog.android.internal.densityValue
Expand Down Expand Up @@ -118,12 +118,14 @@ public class PostHogReplayIntegration(
}

private val isSessionReplayEnabled: Boolean
get() = PostHog.isSessionReplayActive()
get() = postHog?.isSessionReplayActive() ?: false

// flutter captures snapshots, so we don't need to capture them here
private val isNativeSdk: Boolean
get() = (config.sdkName != "posthog-flutter")

private var postHog: PostHogInterface? = null

private fun addView(
view: View,
added: Boolean = true,
Expand Down Expand Up @@ -280,7 +282,7 @@ public class PostHogReplayIntegration(
// if we batch them, we need to be aware that the order of the events matters
// also because if we send a mouse interaction later, it might be attached to the wrong
// screen
mouseInteractions.capture()
mouseInteractions.capture(postHog)
}
}

Expand Down Expand Up @@ -311,10 +313,11 @@ public class PostHogReplayIntegration(
decorViews.remove(view)
}

override fun install() {
override fun install(postHog: PostHogInterface) {
if (!isSupported()) {
return
}
this.postHog = postHog

// workaround for react native that is started after the window is added
// Curtains.rootViews should be empty for normal apps yet
Expand All @@ -331,6 +334,7 @@ public class PostHogReplayIntegration(

override fun uninstall() {
try {
this.postHog = null
Curtains.onRootViewsChangedListeners -= onRootViewsChangedListener

decorViews.entries.forEach {
Expand Down Expand Up @@ -460,7 +464,7 @@ public class PostHogReplayIntegration(
}

if (events.isNotEmpty()) {
events.capture()
events.capture(postHog)
}

status.lastSnapshot = wireframe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.posthog.android.replay.internal

import android.annotation.SuppressLint
import android.os.Build
import com.posthog.PostHog
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroidConfig
import com.posthog.internal.interruptSafely
import com.posthog.internal.replay.RRPluginEvent
Expand All @@ -18,12 +18,15 @@ internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig
private var logcatThread: Thread? = null

private val isSessionReplayEnabled: Boolean
get() = PostHog.isSessionReplayActive()
get() = postHog?.isSessionReplayActive() ?: false

override fun install() {
private var postHog: PostHogInterface? = null

override fun install(postHog: PostHogInterface) {
if (!config.sessionReplayConfig.captureLogcat || !isSupported()) {
return
}
this.postHog = postHog
val cmd = mutableListOf("logcat", "-v", "threadtime", "*:E")
val sdf = SimpleDateFormat("MM-dd HH:mm:ss.mmm", Locale.ROOT)
cmd.add("-T")
Expand Down Expand Up @@ -65,7 +68,7 @@ internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig
val time = log.time?.time?.time ?: config.dateProvider.currentTimeMillis()
val event = RRPluginEvent("rrweb/console@1", props, time)
// TODO: batch events
listOf(event).capture()
listOf(event).capture(postHog)
}
} catch (e: Throwable) {
// ignore
Expand All @@ -87,6 +90,7 @@ internal class PostHogLogCatIntegration(private val config: PostHogAndroidConfig
}

override fun uninstall() {
this.postHog = null
logcatInProgress = false
logcatThread?.interruptSafely()
}
Expand Down
7 changes: 6 additions & 1 deletion posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,12 @@ public abstract interface annotation class com/posthog/PostHogExperimental : jav
}

public abstract interface class com/posthog/PostHogIntegration {
public abstract fun install ()V
public abstract fun install (Lcom/posthog/PostHogInterface;)V
public abstract fun uninstall ()V
}

public final class com/posthog/PostHogIntegration$DefaultImpls {
public static fun install (Lcom/posthog/PostHogIntegration;Lcom/posthog/PostHogInterface;)V
public static fun uninstall (Lcom/posthog/PostHogIntegration;)V
}

Expand Down Expand Up @@ -229,6 +230,8 @@ public final class com/posthog/PostHogOkHttpInterceptor : okhttp3/Interceptor {
public fun <init> ()V
public fun <init> (Z)V
public synthetic fun <init> (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZLcom/posthog/PostHogInterface;)V
public synthetic fun <init> (ZLcom/posthog/PostHogInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
}

Expand Down Expand Up @@ -564,6 +567,8 @@ public final class com/posthog/internal/replay/RRStyle {

public final class com/posthog/internal/replay/RRUtilsKt {
public static final fun capture (Ljava/util/List;)V
public static final fun capture (Ljava/util/List;Lcom/posthog/PostHogInterface;)V
public static synthetic fun capture$default (Ljava/util/List;Lcom/posthog/PostHogInterface;ILjava/lang/Object;)V
}

public final class com/posthog/internal/replay/RRWireframe {
Expand Down
2 changes: 1 addition & 1 deletion posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public class PostHog private constructor(

config.integrations.forEach {
try {
it.install()
it.install(this)
} catch (e: Throwable) {
config.logger.log("Integration ${it.javaClass.name} failed to install: $e.")
}
Expand Down
4 changes: 3 additions & 1 deletion posthog/src/main/java/com/posthog/PostHogIntegration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ package com.posthog
public interface PostHogIntegration {
/**
* Install the Integration after the SDK is setup
* that requires a posthog instance to capture events
*/
public fun install()
public fun install(postHog: PostHogInterface) {
}

/**
* Uninstall the Integration after the SDK is closed
Expand Down
Loading
Loading