posthog_kit
PostHog product analytics for
DartNative apps, with posthog_flutter's Dart
API. Code written for posthog_flutter ports by changing one import.
// import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:posthog_kit/posthog_kit.dart';
posthog_flutter is a method-channel plugin, which DartNative can't load.
posthog_kit is pure Dart over PostHog's HTTP ingestion API (/batch/):
- an anonymous distinct id that persists across launches,
- an offline queue kept on disk and sent in gzipped batches, with retry and exponential backoff,
Application Installed/Updated/Opened/Backgroundedfrom the app lifecycle,identify,alias,group, super properties, opt-out andpersonProfiles, with the same semantics as PostHog's other SDKs.
Setup
dependencies:
dartnative_path_provider: ^1.0.0 # required, see below
posthog_kit:
hosted: https://dartpub.dev
version: ^0.1.0
dn pub get
posthog_kit keeps its queue in the app-support directory, through
dartnative_path_provider. dn pub get builds that plugin into your app
for you. On a DartNative SDK older than 70531222383, it didn't when the
plugin came in only through another package
(DartNative/dartnative#49):
there, list dartnative_path_provider: ^1.0.0 in your app too. Without it,
posthog_kit keeps events in memory for the session and logs
[PostHog] app-support directory unavailable ….
Android needs minSdk 26, the default of apps created with a current
dn create. An older app on 24 needs minSdk = 26 in
android/app/build.gradle.kts.
Android also needs the INTERNET permission in android/app/src/main/AndroidManifest.xml. The
DartNative template declares it only in the debug and profile
manifests (DartNative/dartnative#50), so a release build without this line can't send anything:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
iOS needs nothing: no Info.plist keys (PostHog's hosts are HTTPS).
Usage
import 'package:dartnative/dartnative.dart';
import 'package:posthog_kit/posthog_kit.dart';
import 'dartnative_plugin_registrant.dart';
Future<void> main() async {
DartNativePluginRegistrant.registerAll();
final config = PostHogConfig('phc_your_project_token')
..host = 'https://eu.i.posthog.com' // default: https://us.i.posthog.com
..appVersion = '1.4.0' // enables Application Updated
..appBuild = '42'
..debug = true;
await Posthog().setup(config);
runApp(const HomeScreen());
}
Then anywhere:
await Posthog().capture(eventName: 'trip_created', properties: {'km': 120});
await Posthog().screen(screenName: '/trips'); // later events carry $screen_name
await Posthog().identify(userId: user.id, userProperties: {'role': 'owner'});
await Posthog().group(groupType: 'organization', groupKey: org.id);
await Posthog().register('environment', 'prod'); // super property
await Posthog().reset(); // sign-out
await Posthog().flush(); // send now
await Posthog().disable(); // opt out (persisted)
A pure-Dart package can't read the app version from the bundle, so pass it
in: PackageInfo.fromPlatform() from dartnative_system has it. Without
appVersion, Application Installed and Application Opened still fire,
but Application Updated can't be detected.
Calls made before setup completes, or after close, are ignored, as in
posthog_flutter.
Screen views
DartNative's NavigatorObserver has no didPush, so there is no
PosthogObserver. Call Posthog().screen(screenName: …) from your
navigation helper where you push routes.
Additions for DartNative
These PostHogConfig fields have no upstream equivalent:
| Field | Purpose |
|---|---|
appVersion, appBuild |
$app_version/$app_build, and install/update detection |
osVersion |
$os_version. Parsed automatically on iOS (26.1). Android exposes no release number to pure Dart, so it is omitted there unless you pass it |
httpClient |
an http.Client to send through (a proxy, a Dio wrapper, a test mock) |
storage |
a PostHogStorage; default FilePostHogStorage under the app-support directory. InMemoryPostHogStorage keeps nothing on disk |
libraryName |
the $lib value, default posthog-dartnative ($lib_version is always sent) |
onLog |
receives debug lines instead of dnLog |
Posthog().queueLength returns the number of events waiting.
PostHogClient is public, so tests can build one with an
InMemoryPostHogStorage and MockClient from package:http/testing.dart,
and drive lifecycle with onForeground() and onBackground().
Delivery
- Events are persisted before
capturecompletes, then sent when 20 are queued (flushAt), every 30 s (flushInterval), when the app goes to the background, onflush(), and at the nextsetup. - 2xx removes a batch. 413 halves the batch; a single event that still gets
413 is dropped. 408, 429 and 5xx are retried 3 times, then the batch is
dropped (429 honours
Retry-After). Network errors are retried without a limit. Other 4xx drop the batch. - Retries back off exponentially (5 s doubling, capped at 5 min, full
jitter). Past
maxQueueSize(1000) the oldest event is dropped. - Each event carries a UUIDv7
uuid, so PostHog deduplicates a batch whose response was lost. Application Openedfires on a resume only after a real background, not afterinactive(a Face ID prompt or Control Center).- The flush on background is best-effort: pure Dart can't hold an iOS background task. Anything unsent is sent on the next launch.
Not supported
posthog_flutter API |
Here |
|---|---|
| Feature flags | isFeatureEnabled → false; getFeatureFlag, getFeatureFlagPayload → null; reloadFeatureFlags is a no-op. The rest isn't provided |
| Session replay, surveys | config flags accepted and ignored (debug warning); widgets not provided |
| Error tracking, logs, push notifications | not provided |
PosthogObserver, PostHogWidget |
not provided; see Screen views |
Rage clicks, dataMode, autocapture |
not provided |
Context properties are thinner than the native SDKs': $os,
$os_version, $device_type, $locale, $timezone, $app_version,
$app_build and $session_id, with no device model, screen size or
network type.
Example
example/lib/main.dart has buttons for capture,
screen, identify, group, reset and flush, and shows every debug line. With
no key it sends to an unreachable local port, so events stay queued across
relaunches:
cd example && dn pub get && dn run
dn run --dart-define=POSTHOG_API_KEY=phc_… # send to PostHog for real
Credits & license
The API mirrors posthog_flutter
5.48.0 by PostHog (MIT, Copyright (c) 2020 PostHog). The wire format and
retry rules follow PostHog's
@posthog/core
(Apache-2.0), used as a reference only; no code was copied from either.
This is an independent pure-Dart implementation. "PostHog" is a trademark of
PostHog Inc.; this package isn't affiliated with or endorsed by it.
posthog_kit's code is MIT, see LICENSE. Full notices are in
THIRD_PARTY_NOTICES.