Dijji Dijji Intelligence·Engagement·Defense
Alpha · v1.1.0-alpha Android · iOS Native crash · install referrer · in-app messages Apache 2.0

Intelligence, engagement, defense.
Flutter, two lines.

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.

01 · InstallOne dependency.

Add to your pubspec.yaml:

dependencies: dijji: ^1.1.0-alpha

Then in lib/main.dart:

import 'package:dijji/dijji.dart'; import 'package:flutter/material.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Dijji.instance.initialize(siteKey: 'ws_abc123'); runApp(const MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( navigatorKey: Dijji.instance.navigatorKey, navigatorObservers: [Dijji.instance.navigatorObserver], home: const HomeScreen(), ); } }

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.

02 · What auto-capturesZero extra code.

EventWhen
app_openFirst foreground after process launch
app_foregroundResume from background
app_backgroundApp pauses or hides
session_start / session_end30-min idle window (configurable)
screen_viewNavigatorObserver fires on every push / replace / pop
app_installFirst launch (via Play Install Referrer on Android)
app_crashAny 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_openedVia 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.

03 · Custom events & identityThe full surface.

// Custom events Dijji.instance.track('signup_completed', properties: {'plan': 'pro'}); // Identity (after login) Dijji.instance.identify('user_42', traits: {'plan': 'pro'}); // User properties (super-properties — attached to every event) Dijji.instance.setUserProperty('role', 'admin'); Dijji.instance.setUserProperties({'plan': 'pro', 'tier': 'enterprise'}); Dijji.instance.unsetUserProperty('plan'); // Manual screen (rare — auto-capture covers most cases) Dijji.instance.screen('Pricing'); // Privacy await Dijji.instance.optOut(); await Dijji.instance.optIn(); await Dijji.instance.reset(); // logout — flushes then rotates visitor_id // Force flush before known exit await Dijji.instance.flush();

04 · Crash captureDart and native, handled.

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:

void main() { Dijji.instance.runGuarded(() { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); }); }

Native crashes are captured by the plugin layer:

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.

05 · Push notifications & FirebaseOne-line FCM/APNs.

Push token registration is optional — not every app needs notifications. Two integration paths:

Recommended: dijji_firebase companion

// pubspec.yaml dependencies: dijji: ^1.1.0-alpha dijji_firebase: ^1.0.0-alpha firebase_core: ^3.6.0 firebase_messaging: ^15.1.0 // main.dart import 'package:dijji/dijji.dart'; import 'package:dijji_firebase/dijji_firebase.dart'; import 'package:firebase_core/firebase_core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); await Dijji.instance.initialize(siteKey: 'ws_abc123'); await DijjiFirebase.instance.attach(); runApp(const MyApp()); }

After 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.

Or: bring your own

If you already manage Firebase yourself, just feed Dijji the token + tap callbacks:

final token = await FirebaseMessaging.instance.getToken(); if (token != null) await Dijji.instance.registerPushToken(token); FirebaseMessaging.onMessage.listen((msg) { Dijji.instance.trackPushEvent('push_received', pushId: msg.data['dijji_push_id']); });

06 · Install attributionPlay Install Referrer.

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.

07 · In-app messagesSeven formats.

Cards composed in the Dijji dashboard render automatically through Flutter's overlay. Required wiring (already shown above):

v1.1.4-alpha ships seven format kinds. Each is server-driven via the standard action_config JSON; no host-app code per kind.

KindRenders asRequired configOptional config
in_app_bannerTop/bottom strip, slides in, auto-dismisstitle or bodycta_text, cta_url, image_url (40px thumb), position, duration_ms
in_app_bottom_sheetSlide-up sheet, drag-to-dismisstitlebody, cta_text, cta_url, image_url (16:9 hero)
in_app_modalCentred card with backdroptitlebody, cta_text, cta_url, image_url (16:9 top)
in_app_heroFull-bleed takeover, 4:3 image, dual CTAtitlebody, cta_text, cta_url, secondary_cta_text, image_url
in_app_nps0–10 score sheet, fires __dijji_nps_submittedquestionlow_label, high_label, thanks
in_app_reactionsEmoji bar, fires __dijji_reaction_submittedquestion, emojis (array of 2–5)thanks
in_app_countdownLive ticker modal until deadlinetitle, deadlinebody, 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.

08 · ConfigurationSensible defaults.

FieldDefaultNotes
endpointhttps://dijji.comOverride for staging
autoCaptureScreenstrueDisables the NavigatorObserver if false
sessionTimeout30 minIdle window that ends a session
flushInterval30 sHow often the queue ships in foreground
inboxPollInterval30 sHow often we poll for in-app messages
maxQueueSize500Coerced into 50–5000
captureCrashestrueDart errors
captureNativeCrashestrueJVM uncaught + iOS NSException + POSIX signals
captureInstallReferrertrueAndroid only; no-op on iOS
renderInAppMessagestrueSet false to handle messages yourself
debugfalseVerbose log output

09 · Parity with native SDKsWhere we match.

CapabilityAndroidiOSFlutter
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.

10 · PrivacyNo cookies. No PII.

11 · RoadmapWhat's next.

VersionCapability
v1.0Pure-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.2Background isolate flushing — HTTP off the main isolate so frame-time isn't paid for the flush
v1.3sqflite-backed durable queue (scales past 500 events for offline-heavy apps); opt-in IDFV/AAID for ad attribution
v2.0Symbolicated stack traces (server-side dSYM / ProGuard mapping pipeline)

12 · FAQHonest answers.

Will this conflict with Firebase Crashlytics or Sentry?

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.

Why a plugin instead of pure-Dart?

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.

Will 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.

What about Flutter web?

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.

Does the SDK survive an app force-quit?

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.

How big is the SDK in my final APK / IPA?

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.