From 9fd114f850357c1230ef649a71c8431bc784b16c Mon Sep 17 00:00:00 2001 From: "wizard-ci-bot[bot]" <254716194+wizard-ci-bot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:15:50 +0000 Subject: [PATCH] wizard-ci: android/Jetchat --- .../integration-android/.posthog-wizard | 0 .../integration-android/references/android.md | 857 ++++++++++++++++++ .../references/identify-users.md | 307 +++++++ .../android/Jetchat/.env.example | 3 + .../android/Jetchat/.gitignore | 1 + .../android/Jetchat/app/build.gradle.kts | 19 + .../Jetchat/app/src/main/AndroidManifest.xml | 2 + .../compose/jetchat/JetchatApplication.kt | 60 ++ .../example/compose/jetchat/MainViewModel.kt | 3 + .../example/compose/jetchat/NavActivity.kt | 6 + .../jetchat/conversation/Conversation.kt | 9 + .../compose/jetchat/conversation/UserInput.kt | 6 + .../android/Jetchat/gradle/libs.versions.toml | 2 + 13 files changed, 1275 insertions(+) create mode 100644 apps/basic-integration/android/Jetchat/.claude/skills/integration-android/.posthog-wizard create mode 100644 apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/android.md create mode 100644 apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/identify-users.md create mode 100644 apps/basic-integration/android/Jetchat/.env.example create mode 100644 apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/JetchatApplication.kt diff --git a/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/.posthog-wizard b/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/.posthog-wizard new file mode 100644 index 000000000..e69de29bb diff --git a/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/android.md b/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/android.md new file mode 100644 index 000000000..19e2f9a3e --- /dev/null +++ b/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/android.md @@ -0,0 +1,857 @@ +> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt + +# Android - Docs + +Copy page + +# Android - Docs + +It uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes asynchronously, making it perfect to use in any part of your mobile app. + +## Installation + +The best way to install the PostHog Android library is with a build system like [Gradle](https://gradle.org/). This ensures you can easily upgrade to the latest versions. + +All you need to do is add the `posthog-android` module to your App's `build.gradle` or `build.gradle.kts`: + +PostHog AI + +### app/build.gradle + +```gradle +dependencies { + implementation 'com.posthog:posthog-android:3.+' +} +``` + +### app/build.gradle.kts + +```kotlin +dependencies { + implementation("com.posthog:posthog-android:3.+") +} +``` + +### Configuration + +The best place to initialize the client is in your `Application` subclass. + +Kotlin + +PostHog AI + +```kotlin +import android.app.Application +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig +class SampleApp : Application() { + companion object { + const val POSTHOG_API_KEY = "" + // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' + const val POSTHOG_HOST = "https://us.i.posthog.com" + } + override fun onCreate() { + super.onCreate() + val config = PostHogAndroidConfig( + apiKey = POSTHOG_API_KEY, + host = POSTHOG_HOST + ) + PostHogAndroid.setup(this, config) + } +} +``` + +## Capturing events + +You can send custom events using `capture`: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.capture(event = "user_signed_up") +``` + +> **Tip:** We recommend using a `[object] [verb]` format for your event names, where `[object]` is the entity that the behavior relates to, and `[verb]` is the behavior itself. For example, `project created`, `user signed up`, or `invite sent`. + +### Setting event properties + +Optionally, you can include additional information with the event by including a [properties](/docs/data/events.md#event-properties) object: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.capture( + event = "user_signed_up", + properties = mapOf( + "login_type" to "email", + "is_free_trial" to true + ) +) +``` + +### Autocapture + +PostHog autocapture automatically tracks the following events for you: + +- **Application Opened** - when the app is opened from a closed state or when the app comes to the foreground. (e.g. from the app switcher) +- **Deep Link Opened** - when the app is opened from a deep link. +- **Application Backgrounded** - when the app is sent to the background by the user. +- **Application Installed** - when the app is installed. +- **Application Updated** - when the app is updated. +- **$screen** - when the user navigates. (if using `android.app.Activity`) +- **$exception** - when uncaught exception autocapture is enabled. To use this, enable [Android error tracking](/docs/error-tracking/installation/android.md) and exception autocapture in the SDK config. + +### Capturing screen views + +With [`captureScreenViews = true`](/docs/libraries/android.md#all-configuration-options), PostHog will try to record all screen changes automatically. + +The `screenTitle` will be the [``](https://developer.android.com/guide/topics/manifest/activity-element)'s `android:label`, if not set it'll fallback to the [``](https://developer.android.com/guide/topics/manifest/application-element)'s `android:label` or the [``](https://developer.android.com/guide/topics/manifest/activity-element)'s `android:name`. + +XML + +PostHog AI + +```xml + +``` + +If you want to manually send a new screen capture event, use the `screen` function. + +This function requires a `screenTitle`. You may also pass in an optional `properties` object. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.screen( + screenTitle = "Dashboard", + properties = mapOf( + "background" to "blue", + "hero" to "superhog" + ) +) +``` + +## Identifying users + +> We highly recommend reading our section on [Identifying users](/docs/integrate/identifying-users.md) to better understand how to correctly use this method. + +Using `identify`, you can associate events with specific users. This enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms. + +An `identify` call has the following arguments: + +- **distinctId:** Required. A unique identifier for your user. Typically either their email or database ID. +- **userProperties:** Optional. A dictionary with key:value pairs to set the [person properties](/docs/product-analytics/person-properties.md) +- **userPropertiesSetOnce:** Optional. Similar to `userProperties`. [See the difference between `userProperties` and `userPropertiesSetOnce`](/docs/product-analytics/person-properties.md#what-is-the-difference-between-set-and-set_once) + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.identify( + distinctId = distinctID, + userProperties = mapOf( + "name" to "Max Hedgehog", + "email" to "max@hedgehogmail.com" + ), + userPropertiesSetOnce = mapOf( + "date_of_first_log_in" to "2024-03-01" + ), +) +``` + +You should call `identify` as soon as you're able to. Typically, this is after your user logs in. This ensures that events sent during your user's sessions are correctly associated with them. + +When you call `identify`, all previously tracked anonymous events will be linked to the user. + +## Get the current user's distinct ID + +You may find it helpful to get the current user's distinct ID. For example, to check whether you've already called `identify` for a user or not. + +To do this, call `distinctId()`. This returns either the ID automatically generated by PostHog or the ID that has been passed by a call to `identify()`. + +## Tracing headers + +Use `tracingHeaders` to connect Android network requests to backend events, errors, and LLM traces captured by a server-side PostHog SDK. Tracing headers are added by the `PostHogOkHttpInterceptor`, so install the interceptor on each `OkHttpClient` whose requests should include PostHog context. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHogOkHttpInterceptor +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig +import okhttp3.OkHttpClient +val config = PostHogAndroidConfig( + apiKey = POSTHOG_API_KEY, + host = POSTHOG_HOST, +).apply { + tracingHeaders = listOf("api.example.com") +} +PostHogAndroid.setup(this, config) +val okHttpClient = OkHttpClient.Builder() + .addInterceptor(PostHogOkHttpInterceptor()) + .build() +``` + +Hostnames are matched exactly and should not include protocols, paths, ports, or wildcard subdomains. Matching OkHttp requests include `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` when those values are available. + +## Alias + +Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend. + +In this case, you can use `alias` to assign another distinct ID to the same user. + +Kotlin + +PostHog AI + +```kotlin +/** + * Create an alias for the current user. + */ +PostHog.alias("distinct_id") +``` + +We strongly recommend reading our docs on [alias](/docs/product-analytics/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. + +## Anonymous and identified events + +PostHog captures two types of events: [**anonymous** and **identified**](/docs/data/anonymous-vs-identified-events.md) + +**Identified events** enable you to attribute events to specific users, and attach [person properties](/docs/product-analytics/person-properties.md). They're best suited for logged-in users. + +Scenarios where you want to capture identified events are: + +- Tracking logged-in users in B2B and B2C SaaS apps +- Doing user segmented product analysis +- Growth and marketing teams wanting to analyze the *complete* conversion lifecycle + +**Anonymous events** are events without individually identifiable data. They're best suited for [web analytics](/docs/web-analytics.md) or apps where users aren't logged in. + +Scenarios where you want to capture anonymous events are: + +- Tracking a marketing website +- Content-focused sites +- B2C apps where users don't sign up or log in + +Under the hood, the key difference between identified and anonymous events is that for identified events we create a [person profile](/docs/data/persons.md) for the user, whereas for anonymous events we do not. + +> **Important:** Due to the reduced cost of processing them, anonymous events can be up to 4x cheaper than identified ones, so we recommended you only capture identified events when needed. + +### How to capture anonymous events + +The Android SDK captures anonymous events by default. However, this may change depending on your `personProfiles` [config](/docs/libraries/android.md#all-configuration-options) when initializing PostHog: + +1. `personProfiles = PersonProfiles.IDENTIFIED_ONLY` *(recommended)* *(default)* - Anonymous events are captured by default. PostHog only captures identified events for users where [person profiles](/docs/data/persons.md) have already been created. + +2. `personProfiles = PersonProfiles.ALWAYS` - Capture identified events for all events. + +3. `personProfiles = PersonProfiles.NEVER` - Capture anonymous events for all events. + +For example: + +Kotlin + +PostHog AI + +```kotlin +val config = PostHogAndroidConfig( + apiKey = POSTHOG_API_KEY, + host = POSTHOG_HOST, +).apply { + personProfiles = PersonProfiles.IDENTIFIED_ONLY +} +``` + +### How to capture identified events + +If you've set the [`personProfiles` config](/docs/libraries/android.md#all-configuration-options) to `IDENTIFIED_ONLY` (the default option), anonymous events are captured by default. Then, to capture identified events, call any of the following functions: + +- [`identify()`](/docs/product-analytics/identify.md) +- [`alias()`](/docs/product-analytics/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) +- [`group()`](/docs/product-analytics/group-analytics.md) + +When you call any of these functions, it creates a [person profile](/docs/data/persons.md) for the user. Once this profile is created, all subsequent events for this user will be captured as identified events. + +Alternatively, you can set `personProfiles` to `ALWAYS` to capture identified events by default. + +## Setting person properties + +To set [properties](/docs/product-analytics/person-properties.md) on your users via an event, you can leverage the event properties `userProperties` and `userPropertiesSetOnce`. + +When capturing an event, you can pass a property called `userProperties` as an event property, and specify its value to be an object with properties to be set on the user that will be associated with the user who triggered the event. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.capture( + event = "button_b_clicked", + properties = mapOf("color" to "blue"), + userProperties = mapOf( + "string" to "value1", + "integer" to 2 + ) +) +``` + +`userPropertiesSetOnce` works just like `userProperties`, except that it will **only set the property if the user doesn't already have that property set**. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.capture( + event = "button_b_clicked", + properties = mapOf("color" to "blue"), + userPropertiesSetOnce = mapOf( + "string" to "value1", + "integer" to 2 + ) +) +``` + +## Super Properties + +Super Properties are properties associated with events that are set once and then sent with every `capture` call, be it a `$screen`, or anything else. + +They are set using `PostHog.register`, which takes a key and value, and they persist across sessions. + +For example, take a look at the following call: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.register("team_id", 22) +``` + +The call above ensures that every event sent by the user will include `"team_id": 22`. This way, if you filtered events by property using `team_id = 22`, it would display all events captured on that user after the `PostHog.register` call, since they all include the specified Super Property. + +However, please note that this does not store properties against the User, only against their events. To store properties against the User object, you should use `PostHog.identify`. More information on this can be found on the [Sending User Information section](#sending-user-information). + +### Removing stored Super Properties + +Super Properties are persisted across sessions so you have to explicitly remove them if they are no longer relevant. In order to stop sending a Super Property with events, you can use `PostHog.unregister`, like so: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.unregister("team_id") +``` + +This will remove the Super Property and subsequent events will not include it. + +If you are doing this as part of a user logging out you can instead simply use `PostHog.reset` which takes care of clearing all stored Super Properties and more. + +## Opt out of data capture + +You can completely opt-out users from data capture. To do this, there are two options: + +1. Opt users out by default by setting `optOut` to `true` in your PostHog config: + +Kotlin + +PostHog AI + +```kotlin +val config = PostHogAndroidConfig( + apiKey = "", + host = "https://us.i.posthog.com" +) +config.optOut = true +PostHogAndroid.setup(this, config) +``` + +2. Opt users out on a per-person basis by calling `optOut()`: + +Kotlin + +PostHog AI + +```kotlin +PostHog.optOut() +``` + +Similarly, you can opt users in: + +Kotlin + +PostHog AI + +```kotlin +PostHog.optIn() +``` + +To check if a user is opted out: + +Kotlin + +PostHog AI + +```kotlin +PostHog.isOptOut() +``` + +## Flush + +You can configure how many events queue before flushing with `flushAt`. Setting this to `1` will send events immediately and will use more battery. The default is `20`. + +You can also configure the flush interval with `flushIntervalSeconds` (default `30`), after which queued events are sent regardless of how many have been gathered: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.android.PostHogAndroidConfig +val config = PostHogAndroidConfig(apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST).apply { + flushAt = 20 + flushIntervalSeconds = 30 +} +``` + +You can also manually flush the queue to start sending events immediately instead of waiting for the next batch: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.flush() +``` + +Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee. + +## Reset after logout + +To reset the user's ID and anonymous ID, call `reset`. Usually you would do this right after the user logs out. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.reset() +``` + +## Feature Flags + +PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them. + +### Boolean feature flags + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +val result = PostHog.getFeatureFlagResult("flag-key") +if (result?.enabled == true) { + // Do something differently for this user + // Optional: fetch the payload from the same evaluation result + val matchedFlagPayload = result.payload +} +``` + +### Multivariate feature flags + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +val result = PostHog.getFeatureFlagResult("flag-key") +if (result?.variant == "variant-key") { // replace "variant-key" with the key of your variant + // Do something differently for this user + // Optional: fetch the payload from the same evaluation result + val matchedFlagPayload = result.payload +} +``` + +### Inspecting all feature flags + +You can inspect all currently loaded feature flags with `PostHog.getAllFeatureFlags()`. It returns each flag's `key`, `enabled` state, `variant`, and `payload`, and does not send a `$feature_flag_called` event, so calling it won't affect your experiment results or flag usage analytics: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.getAllFeatureFlags()?.forEach { flag -> + println("${flag.key} ${flag.enabled} ${flag.variant} ${flag.payload}") +} +``` + +### Ensuring flags are loaded before usage + +Every time a user opens the app, we send a request in the background to fetch the feature flags that apply to that user. We store those flags in the storage. + +This means that for most screens, the feature flags are available immediately – **except for the first time a user visits**. + +To handle this, you can use the `onFeatureFlags` callback to wait for the feature flag request to finish: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +import com.posthog.android.PostHogAndroidConfig +import com.posthog.PostHogOnFeatureFlags +// During SDK initialization +val config = PostHogAndroidConfig(apiKey = "").apply { + onFeatureFlags = PostHogOnFeatureFlags { + if (PostHog.isFeatureEnabled("flag-key")) { + // do something + } + } +} +// And/or after the SDK is initialized +PostHog.reloadFeatureFlags { + if (PostHog.isFeatureEnabled("flag-key")) { + // do something + } +} +``` + +### Reloading feature flags + +Feature flag values are cached. If something has changed with your user and you'd like to refetch their flag values, call: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.reloadFeatureFlags() +``` + +### Tracking feature usage + +To track when someone sees or interacts with a feature, use `captureFeatureView` and `captureFeatureInteraction`. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.captureFeatureView("flag-key", flagVariant = "variant-key") +PostHog.captureFeatureInteraction("flag-key", flagVariant = "variant-key") +``` + +### Bootstrapping flags + +Since there is a delay between initializing PostHog and fetching feature flags, feature flags are not always available immediately. This makes them unusable if you want to do something like redirecting a user to a different page based on a feature flag. + +To have your feature flags available immediately, you can initialize PostHog with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from PostHog, it will use those flag values instead of bootstrapped ones. + +Set `config.bootstrap` before calling `setup()` to seed identity and flag values before the first `/flags` response (requires Android SDK `3.55.0`+): + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHogBootstrapConfig +val config = PostHogAndroidConfig(apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST) +config.bootstrap = PostHogBootstrapConfig( + distinctId = "distinct_id_of_your_user", + isIdentifiedId = true, + featureFlags = mapOf( + "flag-1" to true, + "variant-flag" to "control" + ) +) +PostHogAndroid.setup(this, config) +``` + +- **Bootstrapped identity applies during setup.** On a fresh install, setting it before `setup()` means events captured synchronously during initialization (like `Application Installed`) carry your distinct ID instead of the SDK-generated UUID. + - An **anonymous** bootstrap (`isIdentifiedId: false`, the default) seeds the anonymous ID only when none is persisted yet. Once an anonymous ID exists on disk, or the person has been identified, the SDK ignores it. + - An **identified** bootstrap (`isIdentifiedId: true`) is for a signed-in identity available to your app (for example, from a backend session token). On a fresh install, it seeds the distinct ID, marks the person identified, and generates a separate device ID. On a returning install, a matching anonymous ID is marked identified without emitting `$identify`; a different anonymous ID is merged via `identify()` when person profiles are enabled. This emits `$identify` unless capturing is opted out. A different, already-identified person is left untouched. +- **Bootstrapped flags are served until the first `/flags` response, then replaced.** A complete `/flags` response takes over entirely, so bootstrapped-only keys don't persist past it. Only *enabled* flags are seeded: a `true` boolean or a non-empty variant string. A `false` or empty value is dropped, matching posthog-js. Seed payloads with the separate `featureFlagPayloads` option. Flag values and payloads must be JSON-serializable, or they're dropped. Bootstrapped flags are cleared on `reset()`. + +The feature-flags-loaded signal fires as soon as bootstrapped flags are applied, so startup logic can read them immediately. These SDKs don't support the `sessionID` bootstrap option. When person profiles are set to `never`, the SDK preserves a different anonymous identity instead of merging it into an identified bootstrap. + +See the [SDK bootstrapping guide](/docs/libraries/bootstrapping.md) for the cross-SDK overview. + +## Experiments (A/B tests) + +Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code: + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +if (PostHog.getFeatureFlag("experiment-feature-flag-key") == "variant-name") { + // do something +} +``` + +It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). + +## Group analytics + +Group analytics allows you to associate the events for that person's session with a group (e.g. teams, organizations, etc.). Read the [Group Analytics](/docs/user-guides/group-analytics.md) guide for more information. + +> **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). + +- Associate the events for this session with a group + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +// organization is the group type, company_id_in_your_db is the group ID +PostHog.group( + type = "company", + key = "company_id_in_your_db" +) +``` + +- Associate the events for this session with a group AND update the properties of that group + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PostHog +PostHog.group( + type = "company", + key = "company_id_in_your_db", + groupProperties = mapOf("name" to "Awesome Inc.") +) +``` + +The `name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID will be used instead. + +## Error tracking + +To set up error tracking in your project, see the [error tracking docs](/docs/error-tracking.md). + +## Logs + +To set up [logs](/docs/logs.md) in your Android app, follow the [Android logs installation guide](/docs/logs/installation/android.md). The SDK exposes `PostHog.logger.{trace,debug,info,warn,error,fatal}` for sending structured records to PostHog Logs, with batching, offline persistence, and a rate cap built in. + +> **Minimum version:** `com.posthog:posthog-android@3.46.0` or later. + +## Session replay + +To set up [session replay](/docs/session-replay/mobile.md) in your project, all you need to do is install the Android SDK, enable "Record user sessions" in [your project settings](https://us.posthog.com/settings/project-replay) and enable the `sessionReplay` option. + +## Surveys + +To set up surveys, follow the [additional installation instructions for Android](/docs/surveys/installation/android.md). Surveys launched with [popover presentation](/docs/surveys/creating-surveys.md#presentation) are automatically shown to users matching the [display conditions](/docs/surveys/creating-surveys.md#display-conditions) you set up. + +## Offline behavior + +The PostHog Android SDK will continue to capture events when the device is offline. The events are stored in a queue in the device's file storage and are flushed when the device is online. + +- The queue has a maximum size defined by `maxQueueSize` in the configuration. +- When the queue is full, the oldest event is deleted first. +- The queue is flushed when the app is restarted and the device is online. +- When you call [`flush()`](#flush) while the device is offline, it aborts early and the events are not flushed. + +## Debug mode + +If you're not seeing the expected events being captured, the feature flags being evaluated, surveys being shown, or session replay/error tracking behavior, you can enable debug mode to see what's happening. + +You can enable debug mode by setting the `debug` option to `true` in the `PostHogAndroidConfig` object. This will enable verbose logs about the inner workings of the SDK. + +Kotlin + +PostHog AI + +```kotlin +val config = PostHogAndroidConfig(apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST).apply { + debug = true + // ... other config options +} +``` + +## All configuration options + +When creating the PostHog client, pass a `PostHogAndroidConfig`. It inherits the core `PostHogConfig` options and adds Android-specific options. + +Kotlin + +PostHog AI + +```kotlin +import com.posthog.PersonProfiles +import com.posthog.android.PostHogAndroidConfig +val config = PostHogAndroidConfig( + apiKey = POSTHOG_API_KEY, + host = POSTHOG_HOST +).apply { + captureApplicationLifecycleEvents = true + captureScreenViews = true + captureDeepLinks = true + flushAt = 20 + maxQueueSize = 1000 + maxBatchSize = 50 + maxRetries = 3 + flushIntervalSeconds = 30 + debug = false + optOut = false + sendFeatureFlagEvent = true + featureFlagCalledCacheSize = 1000 + preloadFeatureFlags = true + evaluationContexts = listOf("production", "android", "mobile") + setDefaultPersonProperties = true + personProfiles = PersonProfiles.IDENTIFIED_ONLY + reuseAnonymousId = false + sessionReplay = false + errorTrackingConfig.autoCapture = false +} +``` + +### Android-specific options + +| Option | Default | Description | +| --- | --- | --- | +| captureApplicationLifecycleEvents | true | Captures Application Installed, Application Updated, Application Opened, and Application Backgrounded. | +| captureScreenViews | true | Captures $screen for foreground android.app.Activity screens. | +| captureDeepLinks | true | Captures Deep Link Opened with URL/query/referrer properties. | + +### Core options + +| Option | Default | Description | +| --- | --- | --- | +| debug | false | Enables verbose SDK logs in Logcat. You can also call PostHog.debug(true). | +| optOut | false | Prevents data capture when enabled. You can also call PostHog.optOut() and PostHog.optIn(). | +| flushAt | 20 | Number of queued events that triggers a flush. | +| maxQueueSize | 1000 | Maximum number of events kept across memory and disk before FIFO eviction. | +| maxBatchSize | 50 | Maximum number of events sent in one batch request. | +| maxRetries | 3 | Maximum retry attempts for failed requests. | +| flushIntervalSeconds | 30 | Maximum delay before queued data is flushed. | +| encryption | null | Optional PostHogEncryption implementation for encrypting persisted queued events. | +| proxy | null | Optional java.net.Proxy for PostHog API requests. | +| getAnonymousId | generated UUID | Optional hook to customize anonymous ID generation. | +| reuseAnonymousId | false | Reuses one anonymous ID across user changes on the same device. | +| personProfiles | PersonProfiles.IDENTIFIED_ONLY | Controls when person profiles are processed: IDENTIFIED_ONLY, ALWAYS, or NEVER. | +| setDefaultPersonProperties | true | Includes default device and app properties in feature flag evaluation requests. | +| releaseIdentifier | app/version fallback | Release identifier used by error tracking and uploaded ProGuard/R8 mappings. The Android Gradle plugin can inject this automatically. | +| tracingHeaders | null | Exact hostnames that should receive PostHog tracing headers when using PostHogOkHttpInterceptor. | + +### Feature flag options + +| Option | Default | Description | +| --- | --- | --- | +| sendFeatureFlagEvent | true | Sends $feature_flag_called when a feature flag is evaluated. | +| featureFlagCalledCacheSize | 1000 | Number of feature flag calls cached for deduplicating $feature_flag_called events. | +| preloadFeatureFlags | true | Fetches feature flags automatically during setup. | +| evaluationContexts | null | Context tags that constrain which feature flags are evaluated. Available in version 3.29.1+. The legacy evaluationEnvironments option is available in version 3.24.0+. | +| onFeatureFlags | null | Callback invoked when feature flags are loaded. | + +### Product configuration objects + +| Option | Default | Description | +| --- | --- | --- | +| sessionReplay | false | Enables session replay when project settings also allow recording. | +| sessionReplayConfig | PostHogSessionReplayConfig() | Configures masking, screenshots, Logcat capture, sampling, and custom drawable conversion. | +| logs | PostHogLogsConfig() | Configures [Android logs](/docs/logs/installation/android.md). | +| errorTrackingConfig | PostHogErrorTrackingConfig() | Configures error tracking. autoCapture defaults to false; set it to true to autocapture uncaught exceptions when project settings also enable error tracking. | +| surveys | false | Internal/experimental native Android survey support. Native Android survey UI is not fully supported or documented yet. | +| surveysConfig | PostHogSurveysConfig() | Internal/experimental survey display delegate configuration, primarily for hybrid SDKs. | +| bootstrap | null | Seeds identity (distinctId, isIdentifiedId) and feature-flag state (featureFlags, featureFlagPayloads) before the first /flags response. Bootstrapped identity applies to the first session; only enabled flags are served, until the first /flags response replaces them. See [SDK bootstrapping](/docs/libraries/bootstrapping.md#behavior-on-mobile-sdks). | + +### Event filtering with `beforeSend` + +Use `addBeforeSend` to redact, modify, or drop events before they are queued. Return `null` to drop an event. + +Kotlin + +PostHog AI + +```kotlin +config.addBeforeSend { event -> + event.properties?.remove("password") + if (event.event == "internal_debug_event") { + null + } else { + event + } +} +``` + +#### Filtering autocaptured screens + +You can stop specific screens from being autocaptured by filtering them in your before-send hook. Return `null` for any `$screen` event whose `$screen_name` matches a screen you don't want to track, and it's dropped before being sent – keeping unwanted screen views out of your event log. + +Because it's just a function, you can filter however you like – an **ignorelist** (drop the screens you name), an **allowlist** (invert the check to capture only the screens you name), or any custom rule such as a name prefix, a regex, or a check against the event's properties. + +Kotlin + +PostHog AI + +```kotlin +val ignoredScreens = setOf("Splash", "Debug") +config.addBeforeSend { event -> + val screenName = event.properties?.get("$screen_name") as? String + if (event.event == "$screen" && screenName in ignoredScreens) { + null + } else { + event + } +} +``` + +## Push notifications + +The Android SDK can register a device for [Workflows](/docs/workflows.md) push notifications and capture when a user opens one. For setup, including automatic and manual registration, capturing opens, opting out, and identity verification, see [Push notifications](/docs/workflows/push-notifications.md). + +## FAQ + +## What Android API level is required? + +The Android SDK supports Android API 23 and newer. + +## Do I need to declare permissions in the AndroidManifest.xml? + +Usually, no. The SDK declares `android.permission.INTERNET` and `android.permission.ACCESS_NETWORK_STATE`, and Android's manifest merger adds them to your app. The SDK does not declare or require an Android `Service`. + +### Still have questions? + +Ask PostHog AI + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/identify-users.md b/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/identify-users.md new file mode 100644 index 000000000..8647dcb37 --- /dev/null +++ b/apps/basic-integration/android/Jetchat/.claude/skills/integration-android/references/identify-users.md @@ -0,0 +1,307 @@ +> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt + +# Identify users - Docs + +Copy page + +# Identify users - Docs + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +This is straightforward to do when [capturing backend events](/docs/product-analytics/capture-events?tab=Node.js.md), as you associate events to a specific user using a `distinct_id`, which is a required argument. + +However, in the frontend of a [web](/docs/libraries/js/usage.md#capturing-events) or [mobile app](/docs/libraries/ios.md#capturing-events), a `distinct_id` is not a required argument — PostHog's SDKs will generate an anonymous `distinct_id` for you automatically and you can capture events anonymously, provided you use the appropriate [configuration](/docs/libraries/js/usage.md#capturing-anonymous-events). + +To link events to specific users, call `identify`: + +PostHog AI + +### Web + +```javascript +posthog.identify( + 'distinct_id', // Replace 'distinct_id' with your user's unique identifier + { email: 'max@hedgehogmail.com', name: 'Max Hedgehog' } // optional: set additional person properties +); +``` + +### Android + +```kotlin +PostHog.identify( + distinctId = distinctID, // Replace 'distinctID' with your user's unique identifier + // optional: set additional person properties + userProperties = mapOf( + "name" to "Max Hedgehog", + "email" to "max@hedgehogmail.com" + ) +) +``` + +### iOS + +```swift +PostHogSDK.shared.identify("distinct_id", // Replace "distinct_id" with your user's unique identifier + userProperties: ["name": "Max Hedgehog", "email": "max@hedgehogmail.com"]) // optional: set additional person properties +``` + +### React Native + +```jsx +posthog.identify('distinct_id', { // Replace "distinct_id" with your user's unique identifier + email: 'max@hedgehogmail.com', // optional: set additional person properties + name: 'Max Hedgehog' +}) +``` + +### Dart + +```dart +await Posthog().identify( + userId: 'distinct_id', // Replace "distinct_id" with your user's unique identifier + userProperties: { + 'email': 'max@hedgehogmail.com', // optional: set additional person properties + 'name': 'Max Hedgehog', + }, +); +``` + +Events captured after calling `identify` are identified events and this creates a person profile if one doesn't exist already. + +Due to the cost of processing them, anonymous events can be up to 4x cheaper than identified events, so it's recommended you only capture identified events when needed. + +## How identify works + +When a user starts browsing your website or app, PostHog automatically assigns them an **anonymous ID**, which is stored locally. + +Provided you've [configured persistence](/docs/libraries/js/persistence.md) to use cookies or `localStorage`, this enables us to track anonymous users – even across different sessions. + +By calling `identify` with a `distinct_id` of your choice (usually the user's ID in your database, or their email), you link the anonymous ID and distinct ID together. + +Thus, all past and future events made with that anonymous ID are now associated with the distinct ID. + +This enables you to do things like associate events with a user from before they log in for the first time, or associate their events across different devices or platforms. + +Using identify in the backend + +Although you can call `identify` using our backend SDKs, it is used most in frontends. This is because there is no concept of anonymous sessions in the backend SDKs, so calling `identify` only updates person profiles. + +## Best practices when using `identify` + +### 1\. Call `identify` as soon as you're able to + +In your frontend, you should call `identify` as soon as you're able to. + +Typically, this is every time your **app loads** for the first time, and directly after your **users log in**. + +This ensures that events sent during your users' sessions are correctly associated with them. + +You only need to call `identify` once per session, and you should avoid calling it multiple times unnecessarily. + +If you call `identify` multiple times with the same data without reloading the page in between, PostHog will ignore the subsequent calls. + +#### Identify users when the web SDK loads + +If your app already knows the signed-in user when you initialize the JavaScript web SDK, the [`loaded` callback](/docs/libraries/js/config.md) is a convenient place to call `identify`. This identifies the user as soon as the SDK has loaded: + +Web + +PostHog AI + +```javascript +posthog.init('', { + api_host: 'https://us.i.posthog.com', + defaults: '2026-05-30', + loaded: (posthog) => { + if (currentUser?.id) { + posthog.identify(currentUser.id, { + email: currentUser.email, + name: currentUser.name, + }) + } + }, +}) +``` + +In this example, `currentUser` represents user data already available from your authentication system. If your app loads the user asynchronously, call `posthog.identify()` as soon as that data becomes available instead. + +### 2\. Use unique strings for distinct IDs + +If two users have the same distinct ID, their data is merged and they are considered one user in PostHog. Two common ways this can happen are: + +- Your logic for generating IDs does not generate sufficiently strong IDs and you can end up with a clash where 2 users have the same ID. +- There's a bug, typo, or mistake in your code leading to most or all users being identified with generic IDs like `null`, `true`, or `distinctId`. + +PostHog also has built-in protections to stop the most common distinct ID mistakes. + +### 3\. Reset after logout + +If a user logs out on your frontend, you should call `reset()` to unlink any future events made on that device with that user. + +This is important if your users are sharing a computer, as otherwise all of those users are grouped together into a single user due to shared cookies between sessions. + +**We strongly recommend you call `reset` on logout even if you don't expect users to share a computer.** + +You can do that like so: + +PostHog AI + +### Web + +```javascript +posthog.reset() +``` + +### iOS + +```swift +PostHogSDK.shared.reset() +``` + +### Android + +```kotlin +PostHog.reset() +``` + +### React Native + +```jsx +posthog.reset() +``` + +### Dart + +```dart +await Posthog().reset(); +``` + +If you *also* want to reset the `device_id` so that the device will be considered a new device in future events, you can pass `true` as an argument: + +Web + +PostHog AI + +```javascript +posthog.reset(true) +``` + +### 4\. Person profiles and properties + +You'll notice that one of the parameters in the `identify` method is a `properties` object. + +This enables you to set [person properties](/docs/product-analytics/person-properties.md). + +Whenever possible, we recommend passing in all person properties you have available each time you call identify, as this ensures their person profile on PostHog is up to date. + +Person properties can also be set being adding a `$set` property to a event `capture` call. + +**\`$set\` and \`$set\_once\` aren't stored on events** + +These properties only tell PostHog how to update person data during ingestion — they aren't kept on the stored event, so you can't filter, break down, or query events by them. To query the values you set, use [person properties](/docs/product-analytics/person-properties.md) instead. + +See our [person properties docs](/docs/product-analytics/person-properties.md) for more details on how to work with them and best practices. + +### 5\. Use deep links between platforms + +We recommend you call `identify` [as soon as you're able](#1-call-identify-as-soon-as-youre-able), typically when a user signs up or logs in. + +This doesn't work if one or both platforms are unauthenticated. Some examples of such cases are: + +- Onboarding and signup flows before authentication. +- Unauthenticated web pages redirecting to authenticated mobile apps. +- Authenticated web apps prompting an app download. + +In these cases, you can use a [deep link](https://developer.android.com/training/app-links/deep-linking) on Android and [universal links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) on iOS to identify users. + +1. Use `posthog.get_distinct_id()` to get the current distinct ID. Even if you cannot call identify because the user is unauthenticated, this will return an anonymous distinct ID generated by PostHog. +2. Add the distinct ID to the deep link as query parameters, along with other properties like UTM parameters. +3. When the user is redirected to the app, parse the deep link and handle the following cases: + +- The mobile app is already authenticated. In this case, call [`posthog.alias()`](/docs/libraries/js/usage.md#alias) with the distinct ID from the web. This associates the two distinct IDs as a single person. +- The mobile app is unauthenticated. In this case, call [`posthog.identify()`](/docs/libraries/js/usage.md#identifying-users) with the distinct ID from the web so pre-login mobile events stay connected to the web session. When the user later logs in on mobile, call `identify()` again with your canonical user ID. + +As long as you associate the distinct IDs with `posthog.identify()` or `posthog.alias()`, you can track events generated across platforms. + +Here's an example implementation for handling deep links from web to mobile: + +PostHog AI + +### iOS + +```swift +import PostHog +class DeepLinkIdentityManager { + static let shared = DeepLinkIdentityManager() + // MARK: - Deep Link Received + func handleDeepLink(_ url: URL, isAuthenticatedOnMobile: Bool) { + guard let webDistinctId = URLComponents(url: url, resolvingAgainstBaseURL: true)? + .queryItems?.first(where: { $0.name == "ph_distinct_id" })?.value else { + return + } + if isAuthenticatedOnMobile { + // The mobile app already knows the current user. + // Alias the incoming web distinct ID to that user. + PostHogSDK.shared.alias(webDistinctId) + } else { + // Reuse the web distinct ID until login on mobile. + PostHogSDK.shared.identify(webDistinctId) + } + } + // MARK: - Login/Signup + func handleLogin(canonicalUserId: String) { + // Switch from the web distinct ID (or a mobile anon ID) + // to your canonical user ID. + PostHogSDK.shared.identify(canonicalUserId) + // Set user properties, track signup event, etc. + } + func handleLogout() { + PostHogSDK.shared.reset() + } +} +``` + +### Android + +```kotlin +import android.net.Uri +import com.posthog.PostHog +object DeepLinkIdentityManager { + // Deep Link Received + fun handleDeepLink(uri: Uri, isAuthenticatedOnMobile: Boolean) { + val webDistinctId = uri.getQueryParameter("ph_distinct_id") ?: return + if (isAuthenticatedOnMobile) { + // The mobile app already knows the current user. + // Alias the incoming web distinct ID to that user. + PostHog.alias(webDistinctId) + } else { + // Reuse the web distinct ID until login on mobile. + PostHog.identify(webDistinctId) + } + } + // Login/Signup + fun handleLogin(canonicalUserId: String) { + // Switch from the web distinct ID (or a mobile anon ID) + // to your canonical user ID. + PostHog.identify(canonicalUserId) + // Set user properties, track signup event, etc. + } + fun handleLogout() { + PostHog.reset() + } +} +``` + +## Further reading + +- [Identifying users docs](/docs/product-analytics/identify.md) +- [How person processing works](/docs/how-posthog-works/ingestion-pipeline.md#2-person-processing) +- [An introductory guide to identifying users in PostHog](/tutorials/identifying-users-guide.md) + +### Still have questions? + +Ask PostHog AI + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/apps/basic-integration/android/Jetchat/.env.example b/apps/basic-integration/android/Jetchat/.env.example new file mode 100644 index 000000000..c6b988ae5 --- /dev/null +++ b/apps/basic-integration/android/Jetchat/.env.example @@ -0,0 +1,3 @@ +# PostHog Android configuration. Export these variables before building the app. +POSTHOG_PROJECT_TOKEN=your_posthog_project_token +POSTHOG_HOST=your_posthog_host diff --git a/apps/basic-integration/android/Jetchat/.gitignore b/apps/basic-integration/android/Jetchat/.gitignore index 834ecd9df..49fbb30bc 100644 --- a/apps/basic-integration/android/Jetchat/.gitignore +++ b/apps/basic-integration/android/Jetchat/.gitignore @@ -8,6 +8,7 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store +.env /build /captures .externalNativeBuild diff --git a/apps/basic-integration/android/Jetchat/app/build.gradle.kts b/apps/basic-integration/android/Jetchat/app/build.gradle.kts index 4cdae3e2d..a737c6713 100644 --- a/apps/basic-integration/android/Jetchat/app/build.gradle.kts +++ b/apps/basic-integration/android/Jetchat/app/build.gradle.kts @@ -26,6 +26,21 @@ android { compileSdk = libs.versions.compileSdk.get().toInt() namespace = "com.example.compose.jetchat" + val posthogEnvironment = rootProject.file(".env").takeIf { it.isFile } + ?.readLines() + ?.mapNotNull { line -> + line.takeUnless { it.isBlank() || it.trimStart().startsWith("#") } + ?.split("=", limit = 2) + ?.takeIf { it.size == 2 } + } + ?.associate { (key, value) -> key.trim() to value.trim().removeSurrounding("\"") } + .orEmpty() + fun posthogValue(name: String) = + providers.environmentVariable(name).orNull ?: posthogEnvironment[name].orEmpty() + + val posthogProjectToken = posthogValue("POSTHOG_PROJECT_TOKEN") + val posthogHost = posthogValue("POSTHOG_HOST") + defaultConfig { applicationId = "com.example.compose.jetchat" minSdk = libs.versions.minSdk.get().toInt() @@ -35,6 +50,8 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + buildConfigField("String", "POSTHOG_PROJECT_TOKEN", "\"$posthogProjectToken\"") + buildConfigField("String", "POSTHOG_HOST", "\"$posthogHost\"") } signingConfigs { @@ -75,6 +92,7 @@ android { } buildFeatures { + buildConfig = true compose = true viewBinding = true } @@ -96,6 +114,7 @@ dependencies { implementation(libs.androidx.glance.material3) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) + implementation(libs.posthog.android) implementation(libs.androidx.activity.compose) diff --git a/apps/basic-integration/android/Jetchat/app/src/main/AndroidManifest.xml b/apps/basic-integration/android/Jetchat/app/src/main/AndroidManifest.xml index b4ea01d94..8804277b5 100644 --- a/apps/basic-integration/android/Jetchat/app/src/main/AndroidManifest.xml +++ b/apps/basic-integration/android/Jetchat/app/src/main/AndroidManifest.xml @@ -22,11 +22,13 @@ android:enableOnBackInvokedCallback="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:name=".JetchatApplication" android:supportsRtl="true" android:theme="@style/Theme.Jetchat.NoActionBar"> diff --git a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/JetchatApplication.kt b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/JetchatApplication.kt new file mode 100644 index 000000000..f6e1536e1 --- /dev/null +++ b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/JetchatApplication.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.jetchat + +import android.app.Application +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig + +class JetchatApplication : Application() { + override fun onCreate() { + super.onCreate() + + val projectToken = BuildConfig.POSTHOG_PROJECT_TOKEN + val host = BuildConfig.POSTHOG_HOST + + if (projectToken.isBlank()) { + if (BuildConfig.DEBUG) { + error( + "POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, " + + "this causes events to be silently missed. This error stops appearing once " + + "POSTHOG_PROJECT_TOKEN is configured", + ) + } + return + } + + if (host.isBlank()) { + if (BuildConfig.DEBUG) { + error( + "POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes " + + "events to be silently missed. This error stops appearing once POSTHOG_HOST is configured", + ) + } + return + } + + val config = PostHogAndroidConfig( + apiKey = projectToken, + host = host, + ).apply { + errorTrackingConfig.autoCapture = true + } + + PostHogAndroid.setup(this, config) + } +} diff --git a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/MainViewModel.kt b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/MainViewModel.kt index 41d7d639b..f03140840 100644 --- a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/MainViewModel.kt +++ b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/MainViewModel.kt @@ -17,6 +17,7 @@ package com.example.compose.jetchat import androidx.lifecycle.ViewModel +import com.posthog.PostHog import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -44,9 +45,11 @@ class MainViewModel : ViewModel() { fun login(username: String, password: String) { // Fake auth: accept anything; password intentionally unused. _loggedInUsername.value = username + PostHog.capture(event = "user_logged_in") } fun logout() { + PostHog.capture(event = "user_logged_out") _loggedInUsername.value = null } } diff --git a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt index ba4840aed..593cc85a3 100644 --- a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt +++ b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt @@ -40,6 +40,7 @@ import androidx.navigation.fragment.NavHostFragment import com.example.compose.jetchat.auth.LoginScreen import com.example.compose.jetchat.components.JetchatDrawer import com.example.compose.jetchat.databinding.ContentMainBinding +import com.posthog.PostHog import kotlinx.coroutines.launch /** @@ -91,6 +92,10 @@ class NavActivity : AppCompatActivity() { selectedMenu = selectedMenu, username = loggedInUsername, onChatClicked = { + PostHog.capture( + event = "chat_channel_selected", + properties = mapOf("channel" to it), + ) findNavController().popBackStack(R.id.nav_home, false) scope.launch { drawerState.close() @@ -98,6 +103,7 @@ class NavActivity : AppCompatActivity() { selectedMenu = it }, onProfileClicked = { + PostHog.capture(event = "profile_opened") val bundle = bundleOf("userId" to it) findNavController().navigate(R.id.nav_profile, bundle) scope.launch { diff --git a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt index 8f5290750..e84b9a012 100644 --- a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt +++ b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt @@ -91,6 +91,7 @@ import com.example.compose.jetchat.R import com.example.compose.jetchat.components.JetchatAppBar import com.example.compose.jetchat.data.exampleUiState import com.example.compose.jetchat.theme.JetchatTheme +import com.posthog.PostHog import kotlinx.coroutines.launch /** @@ -137,6 +138,10 @@ fun ConversationContent( uiState.addMessage( Message(authorMe, clipData.getItemAt(0).text.toString(), timeNow), ) + PostHog.capture( + event = "message_sent", + properties = mapOf("input_method" to "drag_and_drop"), + ) return true } @@ -203,6 +208,10 @@ fun ConversationContent( uiState.addMessage( Message(authorMe, content, timeNow), ) + PostHog.capture( + event = "message_sent", + properties = mapOf("input_method" to "text"), + ) }, resetScroll = { scope.launch { diff --git a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt index cc5df57bd..84d42a806 100644 --- a/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt +++ b/apps/basic-integration/android/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt @@ -105,6 +105,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.compose.jetchat.FunctionalityNotAvailablePopup import com.example.compose.jetchat.R +import com.posthog.PostHog import kotlin.math.absoluteValue import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -446,13 +447,18 @@ private fun UserInputText( onStartRecording = { val consumed = !isRecordingMessage isRecordingMessage = true + if (consumed) { + PostHog.capture(event = "voice_recording_started") + } consumed }, onFinishRecording = { // handle end of recording + PostHog.capture(event = "voice_recording_completed") isRecordingMessage = false }, onCancelRecording = { + PostHog.capture(event = "voice_recording_cancelled") isRecordingMessage = false }, modifier = Modifier.fillMaxHeight(), diff --git a/apps/basic-integration/android/Jetchat/gradle/libs.versions.toml b/apps/basic-integration/android/Jetchat/gradle/libs.versions.toml index b9be095d9..e922ab928 100644 --- a/apps/basic-integration/android/Jetchat/gradle/libs.versions.toml +++ b/apps/basic-integration/android/Jetchat/gradle/libs.versions.toml @@ -48,6 +48,7 @@ maps-compose = "8.0.0" minSdk = "23" okhttp = "5.3.2" play-services-wearable = "19.0.0" +posthog-android = "3.+" robolectric = "4.16.1" roborazzi = "1.57.0" rome = "2.1.0" @@ -152,6 +153,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" } +posthog-android = { module = "com.posthog:posthog-android", version.ref = "posthog-android" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" } roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" }