e4dc3ac30b
Android: uncaught exception handler sends $exception events with stack trace to PostHog, flushes before delegating to default handler. iOS: NSSetUncaughtExceptionHandler captures crashes via PostHogSDK, avoids @MainActor deadlock by calling SDK directly. Common: captureException() available for non-fatal catches app-wide. Platform stubs for jvm/js/wasmJs.
132 lines
4.7 KiB
Kotlin
132 lines
4.7 KiB
Kotlin
package com.tt.honeyDue.analytics
|
|
|
|
import android.app.Application
|
|
import android.util.Log
|
|
import com.posthog.PostHog
|
|
import com.posthog.android.PostHogAndroid
|
|
import com.posthog.android.PostHogAndroidConfig
|
|
|
|
/**
|
|
* Android implementation of PostHog Analytics.
|
|
*/
|
|
actual object PostHogAnalytics {
|
|
// TODO: Replace with your actual PostHog API key
|
|
private const val API_KEY = "YOUR_POSTHOG_API_KEY"
|
|
private const val HOST = "https://us.i.posthog.com"
|
|
|
|
private const val TAG = "PostHogAnalytics"
|
|
|
|
private var isInitialized = false
|
|
private var application: Application? = null
|
|
|
|
/**
|
|
* Initialize PostHog SDK with Application context.
|
|
* Call this in MainActivity.onCreate() before using other methods.
|
|
*/
|
|
fun initialize(application: Application, debug: Boolean = false) {
|
|
if (isInitialized) return
|
|
this.application = application
|
|
|
|
val config = PostHogAndroidConfig(API_KEY, HOST).apply {
|
|
captureScreenViews = false // We'll track screens manually
|
|
captureApplicationLifecycleEvents = true
|
|
captureDeepLinks = true
|
|
this.debug = debug
|
|
|
|
// Session Replay
|
|
sessionReplay = true
|
|
sessionReplayConfig.maskAllTextInputs = true
|
|
sessionReplayConfig.maskAllImages = false
|
|
}
|
|
|
|
PostHogAndroid.setup(application, config)
|
|
isInitialized = true
|
|
}
|
|
|
|
/**
|
|
* Initialize from common code (no-op on Android, use initialize(Application) instead)
|
|
*/
|
|
actual fun initialize() {
|
|
// No-op - Android requires Application context, use initialize(Application) instead
|
|
}
|
|
|
|
actual fun identify(userId: String, properties: Map<String, Any>?) {
|
|
if (!isInitialized) return
|
|
PostHog.identify(userId, userProperties = properties)
|
|
}
|
|
|
|
actual fun capture(event: String, properties: Map<String, Any>?) {
|
|
if (!isInitialized) return
|
|
PostHog.capture(event, properties = properties)
|
|
}
|
|
|
|
/**
|
|
* Capture an exception/crash as a PostHog `$exception` event.
|
|
* Uses PostHog's standard exception property names so exceptions
|
|
* appear correctly in the PostHog Errors dashboard.
|
|
*/
|
|
actual fun captureException(throwable: Throwable, properties: Map<String, Any>?) {
|
|
if (!isInitialized) return
|
|
try {
|
|
val exceptionProps = mutableMapOf<String, Any>(
|
|
"\$exception_type" to (throwable::class.simpleName ?: "Unknown"),
|
|
"\$exception_message" to (throwable.message ?: "No message"),
|
|
"\$exception_stack_trace_raw" to throwable.stackTraceToString()
|
|
)
|
|
if (properties != null) {
|
|
exceptionProps.putAll(properties)
|
|
}
|
|
PostHog.capture("\$exception", properties = exceptionProps)
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Failed to capture exception to PostHog", e)
|
|
}
|
|
}
|
|
|
|
actual fun screen(screenName: String, properties: Map<String, Any>?) {
|
|
if (!isInitialized) return
|
|
PostHog.screen(screenName, properties = properties)
|
|
}
|
|
|
|
actual fun reset() {
|
|
if (!isInitialized) return
|
|
PostHog.reset()
|
|
}
|
|
|
|
actual fun flush() {
|
|
if (!isInitialized) return
|
|
PostHog.flush()
|
|
}
|
|
|
|
/**
|
|
* Install an uncaught exception handler that captures crashes to PostHog
|
|
* before delegating to the default handler (which shows the crash dialog).
|
|
* Call this after initialize() in MainActivity.onCreate().
|
|
*/
|
|
actual fun setupExceptionHandler() {
|
|
if (!isInitialized) return
|
|
|
|
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
|
|
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
|
try {
|
|
PostHog.capture(
|
|
event = "\$exception",
|
|
properties = mapOf(
|
|
"\$exception_type" to (throwable::class.simpleName ?: "Unknown"),
|
|
"\$exception_message" to (throwable.message ?: "No message"),
|
|
"\$exception_stack_trace_raw" to throwable.stackTraceToString(),
|
|
"\$exception_thread" to thread.name,
|
|
"\$exception_is_fatal" to true
|
|
)
|
|
)
|
|
// Flush to ensure the event is sent before the process dies
|
|
PostHog.flush()
|
|
} catch (_: Exception) {
|
|
// Don't let our crash handler crash
|
|
}
|
|
// Call the default handler so the system crash dialog still appears
|
|
defaultHandler?.uncaughtException(thread, throwable)
|
|
}
|
|
Log.d(TAG, "Uncaught exception handler installed")
|
|
}
|
|
}
|