PostHog
PostHog ↗ product analytics for Skip apps on both iOS and Android.
On iOS this wraps the PostHog iOS SDK ↗ (v3.0+). On Android, the Swift code is transpiled to Kotlin via Skip Lite and wraps the PostHog Android SDK ↗ (v3.x).
Add the dependency to your Package.swift file:
let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-posthog.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipPostHog", package: "skip-posthog") ]) ])Configuration
Section titled “Configuration”Initialize PostHog when your app starts:
import SkipPostHog
PostHogSDK.shared.setup( PostHogConfig(apiKey: "phc_your_project_api_key"))For self-hosted instances:
PostHogSDK.shared.setup( PostHogConfig(apiKey: "phc_your_project_api_key", host: "https://your-posthog-instance.com"))Advanced Configuration
Section titled “Advanced Configuration”let config = PostHogConfig(apiKey: "phc_your_project_api_key")
// Event batchingconfig.flushAt = 20 // Send after 20 eventsconfig.flushIntervalSeconds = 30 // Or every 30 secondsconfig.maxQueueSize = 1000 // Max queued eventsconfig.maxBatchSize = 50 // Max events per batch
// Automatic captureconfig.captureApplicationLifecycleEvents = trueconfig.captureScreenViews = true
// Feature flagsconfig.sendFeatureFlagEvent = true // Emit $feature_flag_called eventsconfig.preloadFeatureFlags = true // Preload on startup
// Person profilesconfig.personProfiles = .identifiedOnly // .never, .always, or .identifiedOnly
// Session replayconfig.sessionReplay = true // Enable session recording
// Surveysconfig.surveys = true // Enable in-app surveys
// Debug loggingconfig.debug = true // Enable debug output
// Privacyconfig.optOut = false // Start opted-in (default)
PostHogSDK.shared.setup(config)Event Tracking
Section titled “Event Tracking”// Simple eventPostHogSDK.shared.capture("button_clicked")
// Event with propertiesPostHogSDK.shared.capture( "purchase_completed", properties: [ "product_id": "abc123", "price": 29.99, "currency": "USD" ])
// Event with user properties and groupsPostHogSDK.shared.capture( "subscription_started", properties: ["plan": "premium"], userProperties: ["subscription_tier": "premium"], groups: ["company": "acme-corp"])Screen Tracking
Section titled “Screen Tracking”PostHogSDK.shared.screen("Home Screen")
PostHogSDK.shared.screen( "Product Details", properties: ["product_id": "xyz789", "category": "Electronics"])Exception Tracking
Section titled “Exception Tracking”do { try riskyOperation()} catch { PostHogSDK.shared.captureException(error) PostHogSDK.shared.captureException(error, properties: ["context": "checkout"])}User Identification
Section titled “User Identification”// Identify a userPostHogSDK.shared.identify(distinctId: "user_12345")
// Identify with propertiesPostHogSDK.shared.identify( distinctId: "user_12345", userProperties: [ "email": "user@example.com", "plan": "premium" ], userPropertiesSetOnce: [ "signup_date": "2024-01-15" ])
// Set person properties without creating an identify eventPostHogSDK.shared.setPersonProperties( userPropertiesToSet: ["theme": "dark"], userPropertiesToSetOnce: ["first_seen": "2024-01-15"])
// Alias: link anonymous IDs with identified usersPostHogSDK.shared.alias("user_12345")
// Get current IDslet distinctId = PostHogSDK.shared.getDistinctId()let anonymousId = PostHogSDK.shared.getAnonymousId()
// Reset when user logs outPostHogSDK.shared.reset()Feature Flags
Section titled “Feature Flags”// Check if a feature is enabledif PostHogSDK.shared.isFeatureEnabled("new_checkout_flow") { // Show new checkout flow}
// Get feature flag value (for multivariate flags)if let variant = PostHogSDK.shared.getFeatureFlag("experiment") as? String { switch variant { case "control": // ... case "test": // ... default: break }}
// Get the full feature flag resultif let result = PostHogSDK.shared.getFeatureFlagResult("experiment") { print("Enabled: \(result.enabled)") print("Variant: \(result.variant ?? "none")") print("Payload: \(result.payload ?? "none")")}
// Get flag payloadif let payload = PostHogSDK.shared.getFeatureFlagPayload("experiment") { // Use the JSON payload}
// Reload flags from the serverPostHogSDK.shared.reloadFeatureFlags()
// Reload with completion callbackPostHogSDK.shared.reloadFeatureFlags { print("Feature flags reloaded")}
// Control whether $feature_flag_called events are sentlet enabled = PostHogSDK.shared.isFeatureEnabled("flag", sendFeatureFlagEvent: false)Feature Flag Interaction Tracking
Section titled “Feature Flag Interaction Tracking”Track when users see or interact with features controlled by flags:
PostHogSDK.shared.captureFeatureView(flag: "new-dashboard", flagVariant: "test")PostHogSDK.shared.captureFeatureInteraction(flag: "new-dashboard", flagVariant: "test")Local Flag Evaluation Properties
Section titled “Local Flag Evaluation Properties”Set properties used for local feature flag evaluation:
// Person properties for local evaluationPostHogSDK.shared.setPersonPropertiesForFlags(["plan": "premium"])PostHogSDK.shared.resetPersonPropertiesForFlags()
// Group properties for local evaluationPostHogSDK.shared.setGroupPropertiesForFlags("company", properties: ["industry": "tech"])PostHogSDK.shared.resetGroupPropertiesForFlags("company")PostHogSDK.shared.resetGroupPropertiesForFlags() // Reset all groupsSuper Properties
Section titled “Super Properties”Properties that are sent with every event:
// Register super propertiesPostHogSDK.shared.register(["app_version": "2.1.0", "environment": "production"])
// Remove a super propertyPostHogSDK.shared.unregister("environment")Group Analytics
Section titled “Group Analytics”Associate users with groups (companies, teams, etc.):
PostHogSDK.shared.group(type: "company", key: "acme-corp")
PostHogSDK.shared.group( type: "company", key: "acme-corp", groupProperties: ["name": "Acme Corp", "plan": "enterprise"])Sessions
Section titled “Sessions”PostHogSDK.shared.startSession()PostHogSDK.shared.endSession()
let sessionId = PostHogSDK.shared.getSessionId()let isActive = PostHogSDK.shared.isSessionActive()Session Replay
Section titled “Session Replay”// Check if session replay is recordingif PostHogSDK.shared.isSessionReplayActive() { print("Recording active")}
// Start/stop recordingPostHogSDK.shared.startSessionRecording()PostHogSDK.shared.startSessionRecording(resumeCurrent: true)PostHogSDK.shared.stopSessionRecording()Privacy
Section titled “Privacy”// Opt a user out of trackingPostHogSDK.shared.optOut()
// Check opt-out statusif PostHogSDK.shared.isOptOut() { print("User has opted out")}
// Opt back inPostHogSDK.shared.optIn()Lifecycle
Section titled “Lifecycle”// Flush queued events immediatelyPostHogSDK.shared.flush()
// Close the SDK (releases resources)PostHogSDK.shared.close()
// Enable/disable debug logging at runtimePostHogSDK.shared.debug(true)API Reference
Section titled “API Reference”PostHogSDK
Section titled “PostHogSDK”| Method / Property | Description |
|---|---|
shared | The singleton instance |
setup(_:) | Initialize with a PostHogConfig |
close() | Release SDK resources |
flush() | Send all queued events immediately |
debug(_:) | Enable/disable debug logging |
capture(_:properties:userProperties:groups:) | Track a custom event |
screen(_:properties:) | Track a screen view |
captureException(_:properties:) | Capture an error/exception |
identify(distinctId:userProperties:userPropertiesSetOnce:) | Identify a user |
setPersonProperties(userPropertiesToSet:userPropertiesToSetOnce:) | Set person properties |
alias(_:) | Link anonymous and identified user IDs |
reset() | Clear user data (on logout) |
getDistinctId() | Get current distinct ID |
getAnonymousId() | Get the anonymous ID |
getSessionId() | Get the current session ID |
register(_:) | Set super properties |
unregister(_:) | Remove a super property |
group(type:key:groupProperties:) | Associate with a group |
reloadFeatureFlags() | Refresh feature flags |
reloadFeatureFlags(_:) | Refresh with completion callback |
isFeatureEnabled(_:) | Check if a flag is enabled |
getFeatureFlag(_:) | Get a flag’s value |
getFeatureFlagPayload(_:) | Get a flag’s JSON payload |
getFeatureFlagResult(_:) | Get structured flag result |
captureFeatureView(flag:flagVariant:) | Track feature view |
captureFeatureInteraction(flag:flagVariant:) | Track feature interaction |
setPersonPropertiesForFlags(_:reloadFeatureFlags:) | Set local evaluation properties |
setGroupPropertiesForFlags(_:properties:reloadFeatureFlags:) | Set group properties for flags |
startSession() / endSession() | Manual session control |
isSessionActive() | Whether a session is active |
isSessionReplayActive() | Whether replay is recording |
startSessionRecording(resumeCurrent:) | Start session recording |
stopSessionRecording() | Stop session recording |
optOut() / optIn() / isOptOut() | Privacy controls |
PostHogConfig
Section titled “PostHogConfig”| Property | Type | Default | Description |
|---|---|---|---|
apiKey | String | — | Your PostHog project API key |
host | URL | PostHog Cloud | Custom host URL |
flushAt | Int | 20 | Events before auto-flush |
maxQueueSize | Int | 1000 | Maximum queued events |
maxBatchSize | Int | 50 | Events per network request |
flushIntervalSeconds | TimeInterval | 30 | Auto-flush interval |
sendFeatureFlagEvent | Bool | true | Emit $feature_flag_called |
preloadFeatureFlags | Bool | true | Preload flags on startup |
captureApplicationLifecycleEvents | Bool | false | Track app lifecycle |
captureScreenViews | Bool | false | Auto-track screens |
captureElementInteractions | Bool | false | Autocapture (iOS only) |
personProfiles | PostHogPersonProfiles | .identifiedOnly | Profile creation mode |
sessionReplay | Bool | false | Enable session recording |
surveys | Bool | false | Enable in-app surveys |
debug | Bool | false | Debug logging |
optOut | Bool | false | Start opted-out |
reuseAnonymousId | Bool | true | Reuse anonymous ID on reset |
setDefaultPersonProperties | Bool | true | Set default person properties |
enableSwizzling | Bool | true | Enable method swizzling (iOS only) |
Limitations
Section titled “Limitations”Platform-specific differences:
getAnonymousId()on Android returns the distinct ID (the Android SDK does not expose a separate anonymous ID).captureException()on iOS uses acapture("$exception", ...)workaround because the PostHog iOS SDK’scaptureExceptionis marked@_spiand not publicly accessible.captureElementInteractions(autocapture) is only available on iOS. Setting it on Android is a no-op.enableSwizzlingis only available on iOS. Setting it on Android is a no-op.isSessionActive()on iOS is approximated by checking if a session ID exists.flushIntervalSecondsisTimeInterval(Double) on iOS butInton Android. Fractional seconds are truncated on Android.- Session replay and surveys may require additional setup on each platform. See the PostHog session replay docs ↗ and surveys docs ↗.
remoteConfigis deprecated in newer PostHog SDK versions and is now always enabled.
Building
Section titled “Building”This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.
Testing
Section titled “Testing”The module can be tested using the standard swift test command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with skip test,
which will output a table of the test results for both platforms.