Intelligence·Engagement·Defense
The Dijji SDK for Flutter — one dependency replaces your analytics, crash reporter, push tooling, and in-app messaging stack. Same wire format as the Android and iOS SDKs; events stream into the same database, the same globe, the same AI brief. JVM and POSIX-signal native crash capture on board, no third-party reporter required.
Add to your pubspec.yaml:
Then in lib/main.dart:
That's the entire integration. Auto-capture is on by default. The plugin pulls in its own Android (Kotlin) and iOS (Swift) scaffolding for native crash + install attribution — nothing for you to wire.
| Event | When |
|---|---|
app_open | First foreground after process launch |
app_foreground | Resume from background |
app_background | App pauses or hides |
session_start / session_end | 30-min idle window (configurable) |
screen_view | NavigatorObserver fires on every push / replace / pop |
app_install | First launch (via Play Install Referrer on Android) |
app_crash | Any uncaught Dart error (FlutterError, PlatformDispatcher, runZonedGuarded), plus JVM uncaught (Kotlin / Java) and POSIX signals (SIGSEGV / SIGBUS / SIGABRT / SIGTRAP / fatalError / forced unwrap of nil) |
push_received / push_opened | Via the optional dijji_firebase companion (see §05) |
Every batch carries a rich device snapshot: OS version, device model, locale, network type, battery, dark mode, screen density, font scale, orientation, timezone, days_since_install, session_sequence. Marketers segment against any of those without a custom event.
Dart errors (TypeError, StateError, FormatException, FlutterError build/layout/paint failures) are captured automatically once the SDK is initialized via FlutterError.onError + PlatformDispatcher.onError. For maximum coverage of zoned async errors, wrap your main:
Native crashes are captured by the plugin layer:
Thread.setDefaultUncaughtExceptionHandler (Kotlin / Java uncaught exceptions, OutOfMemoryError, NullPointerException from JNI bridges).NSSetUncaughtExceptionHandler for Objective-C exceptions, plus POSIX signal handlers (SIGABRT, SIGILL, SIGSEGV, SIGFPE, SIGBUS, SIGTRAP) for the pure-Swift crashes NSException misses — fatalError, forced unwrap of nil, array out-of-bounds, preconditionFailure.Marker-on-disk pattern: signal handler writes a tiny JSON blob using only async-signal-safe APIs (open, write, close), then re-raises so the OS / Xcode / debugger reporter still fires. On next launch the plugin reads the marker and forwards to /t/app/crash. Same approach Sentry / Crashlytics / Bugsnag use.
Native captures both Kotlin AND Swift crashes as of v1.1.0-alpha. You can drop firebase_crashlytics if Dijji's coverage is sufficient (it is for most apps). Set captureNativeCrashes: false in DijjiConfig if you want to keep an existing reporter.
Push token registration is optional — not every app needs notifications. Two integration paths:
dijji_firebase companionAfter attach(): token fetched + forwarded to Dijji.instance.registerPushToken; onTokenRefresh wired; push_received fires on every foreground message; push_opened fires on tap (including the cold-start getInitialMessage path); deep links from data.deep_link / data.url are stamped onto the open event for downstream handling.
If you already manage Firebase yourself, just feed Dijji the token + tap callbacks:
On Android, the SDK fetches the install referrer once via Google Play's com.android.installreferrer service and posts to /t/app/install. The dashboard's Installs view shows UTM-stamped install attribution (referrer URL, click timestamp, install timestamp).
Idempotent: a SharedPreferences flag tracks "already sent" so we don't burn quota on every cold start.
iOS has no equivalent public API (Apple's SKAdNetwork is for ad-network-scoped attribution, not first-party install referrer). The Dart call is a no-op on iOS; the dashboard surfaces this honestly.
Cards composed in the Dijji dashboard render automatically through Flutter's overlay. Required wiring (already shown above):
Dijji.instance.navigatorKey attached to your MaterialAppDijji.instance.navigatorObserver in navigatorObserversv1.1.4-alpha ships seven format kinds. Each is server-driven via the standard action_config JSON; no host-app code per kind.
| Kind | Renders as | Required config | Optional config |
|---|---|---|---|
in_app_banner | Top/bottom strip, slides in, auto-dismiss | title or body | cta_text, cta_url, image_url (40px thumb), position, duration_ms |
in_app_bottom_sheet | Slide-up sheet, drag-to-dismiss | title | body, cta_text, cta_url, image_url (16:9 hero) |
in_app_modal | Centred card with backdrop | title | body, cta_text, cta_url, image_url (16:9 top) |
in_app_hero | Full-bleed takeover, 4:3 image, dual CTA | title | body, cta_text, cta_url, secondary_cta_text, image_url |
in_app_nps | 0–10 score sheet, fires __dijji_nps_submitted | question | low_label, high_label, thanks |
in_app_reactions | Emoji bar, fires __dijji_reaction_submitted | question, emojis (array of 2–5) | thanks |
in_app_countdown | Live ticker modal until deadline | title, deadline | body, cta_text, cta_url, image_url, ended_text |
deadline accepts ISO-8601 (2026-05-01T18:00:00Z), relative offsets (+24 hours, +30 minutes, +3 days), or a Unix-seconds number. Image URLs are loaded async with a placeholder; broken or blocked URLs leave the layout intact (no broken-image icon).
If you want to render the messages yourself, set renderInAppMessages: false in DijjiConfig and listen on the inbox stream.
| Field | Default | Notes |
|---|---|---|
endpoint | https://dijji.com | Override for staging |
autoCaptureScreens | true | Disables the NavigatorObserver if false |
sessionTimeout | 30 min | Idle window that ends a session |
flushInterval | 30 s | How often the queue ships in foreground |
inboxPollInterval | 30 s | How often we poll for in-app messages |
maxQueueSize | 500 | Coerced into 50–5000 |
captureCrashes | true | Dart errors |
captureNativeCrashes | true | JVM uncaught + iOS NSException + POSIX signals |
captureInstallReferrer | true | Android only; no-op on iOS |
renderInAppMessages | true | Set false to handle messages yourself |
debug | false | Verbose log output |
| Capability | Android | iOS | Flutter |
|---|---|---|---|
| init / track / identify / props | ✓ | ✓ | ✓ |
| Auto session (30-min idle) | ✓ | ✓ | ✓ |
| Auto screen capture | ✓ Activity | ✓ swizzle | ✓ NavigatorObserver |
| Dart-error capture | n/a | n/a | ✓ |
| Native crash (JVM / NSException / signals) | ✓ | ✓ | ✓ v1.1 |
| Play Install Referrer | ✓ | n/a | ✓ v1.1 |
| FCM / APNs token | ✓ DijjiPush | ✓ DijjiPush | ✓ dijji_firebase |
| In-app banner / sheet / modal | ✓ | ✓ | ✓ |
| In-app hero / nps / reactions / countdown | ✓ v1.2 | ✓ v1.3 | ✓ v1.1.4 |
| image_url across all in-app formats | ✓ v1.2 | ✓ v1.3 | ✓ v1.1.3 |
| Persistent event queue (offline) | in-mem | in-mem | ✓ SharedPreferences |
| Device context bag | ✓ | ✓ | ✓ (carrier null on iOS) |
Flutter SDK is at parity with iOS and ahead of native Android on offline durability. Carrier on iOS isn't readable on iOS 16+ (Apple deprecated the API) — that's a platform limit, not a Flutter SDK gap.
SharedPreferences (Android) / NSUserDefaults (iOS).identify(userId) attaches a stable id only when you explicitly call it.optOut() is honoured locally and on the server; the in-memory queue is dropped on opt-out.| Version | Capability |
|---|---|
v1.0 | Pure-Dart core; auto-capture + custom events + Dart crash + in-app messages |
v1.1 (current) | Native crash on Android + iOS; Play Install Referrer; dijji_firebase companion |
v1.2 | Background isolate flushing — HTTP off the main isolate so frame-time isn't paid for the flush |
v1.3 | sqflite-backed durable queue (scales past 500 events for offline-heavy apps); opt-in IDFV/AAID for ad attribution |
v2.0 | Symbolicated stack traces (server-side dSYM / ProGuard mapping pipeline) |
No, but you may not need them anymore. As of v1.1, Dijji catches native crashes on both platforms via chained handlers — another reporter can run alongside (we never swallow), but if Dijji's coverage is enough, dropping the duplicate is one less SDK in your build. Set captureNativeCrashes: false if you want to keep an existing reporter authoritative.
v1.0 was pure-Dart. v1.1 added native code only for the two things Dart can't do from inside the VM: catch JVM/Objective-C uncaught exceptions and read Play Install Referrer. Everything else (analytics, in-app messages, push, persistence) is still Dart. The native footprint is tiny — ~5KB of Kotlin and ~5KB of Swift.
screen_view work with go_router / auto_route / Beamer?Yes. They all push standard Route objects through a Navigator, and our observer listens at that level. Name your routes meaningfully and they show up correctly in the Dijji dashboard.
Use the Dijji web tracker (<script src="https://dijji.com/d.js">) for Flutter web builds. The Dart SDK is intended for mobile (Android + iOS). Desktop targets work for analytics but native crash + install referrer are mobile-only.
Yes. Queued events persist to SharedPreferences after every enqueue and every failed flush, so they ride through a force-quit. The next launch picks them up at the head of the queue and ships them after the next session start. Same for crash markers — signal-handler crashes write to disk and forward on next launch.
Dart code: ~30 KB compressed. Native plugin: ~10 KB Kotlin + ~10 KB Swift. The transitive dependencies (shared_preferences, device_info_plus, connectivity_plus, battery_plus, package_info_plus, http) are the larger contributors. Compare to a typical Firebase + Crashlytics + Mixpanel stack and Dijji is markedly leaner.