pilotpm_engage (Dart/Flutter)
Pure-Dart SDK for PilotPM Engage: events (identify/track/screen),
push-token registration, and in-app messages. Talks to /api/sdk/v1/* with a
per-workspace write-only ingest key. Full host-app wiring (dual-write beside
Braze, push, in-app render widget): docs/engage-sdk/ELSA-FLUTTER-INTEGRATION.md.
Why pure Dart
No platform-channel code, so it drops into a Flutter app as just another analytics provider — ideal for dual-writing alongside Braze during a migration.
Usage
import 'package:pilotpm_engage/pilotpm_engage.dart';
// At startup (awaits identity + queue hydration):
await PilotPMEngage.instance.configure(
PilotPMConfiguration(apiKey: 'pk_live_…'),
);
// On login:
PilotPMEngage.instance.identify('user-42', traits: {'plan': 'pro'});
// Anywhere (synchronous, fire-and-forget, fail-soft):
PilotPMEngage.instance.track('Lesson Completed', properties: {'score': 9});
PilotPMEngage.instance.screen('Home');
PilotPMEngage.instance.setUserAttributes({'streak_days': 12});
// On logout:
await PilotPMEngage.instance.reset();
Push notifications
The SDK stays pure-Dart — keep your existing FCM/APNs setup and just hand PilotPM the token you already have:
// when FCM/APNs gives you a token (and on every refresh):
await PilotPMEngage.instance.registerPushToken(token, platform: 'ios'); // or 'android'
// on logout / OS revocation:
await PilotPMEngage.instance.unregisterPushToken(token, platform: 'ios');
OS push-permission attribute (segmentable)
Report the OS notification-permission state so marketing can segment on it
(profile field os_push_permission; e.g. a "push disabled" in-app campaign).
This is the OS permission, not the marketing subscription state. With
firebase_messaging (the ELSA wiring):
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:pilotpm_engage/pilotpm_engage.dart';
PilotPMPushPermission _mapPermission(AuthorizationStatus s) {
switch (s) {
case AuthorizationStatus.authorized:
return PilotPMPushPermission.authorized;
case AuthorizationStatus.denied:
return PilotPMPushPermission.denied;
case AuthorizationStatus.provisional:
return PilotPMPushPermission.provisional;
case AuthorizationStatus.notDetermined:
return PilotPMPushPermission.notDetermined;
}
}
// On login — attach it to the identify:
final settings = await FirebaseMessaging.instance.getNotificationSettings();
PilotPMEngage.instance.identify(
userId,
attributes: PilotPMUserAttributes(
pushPermission: _mapPermission(settings.authorizationStatus),
),
);
// On change (after the permission prompt / returning from Settings) — re-send:
PilotPMEngage.instance.setPushPermission(
_mapPermission(settings.authorizationStatus),
);
Applied server-side for identified users only; last write (by event time) wins.
In-app messages
Fetch eligible messages (the server applies targeting + impression caps + dismissal suppression) and report interactions:
final messages = await PilotPMEngage.instance
.fetchInAppMessages(trigger: 'app_open', locale: 'vi'); // locale optional
for (final m in messages) {
// render m.title / m.body / m.imageUrl / m.ctaLabel, then:
await PilotPMEngage.instance
.reportInAppEvent(m.id, PilotPMInAppEventType.impression);
// on CTA tap: reportInAppEvent(m.id, PilotPMInAppEventType.click) + route m.ctaDeeplink
// on close: reportInAppEvent(m.id, PilotPMInAppEventType.dismiss)
}
A copy-paste reference render widget is in
docs/engage-sdk/ELSA-FLUTTER-INTEGRATION.md.
Durable storage (recommended in production)
The default store is in-memory (events batch within a session but don't survive an
app kill). For durability across restarts, pass a shared_preferences-backed
adapter:
class PrefsStore implements PilotPMStore {
final SharedPreferences prefs;
PrefsStore(this.prefs);
@override
Future<String?> getString(String k) async => prefs.getString(k);
@override
Future<void> setString(String k, String? v) async =>
v == null ? prefs.remove(k) : prefs.setString(k, v);
}
await PilotPMEngage.instance.configure(config, store: PrefsStore(prefs));
Drop-in beside Braze (Elsa migration)
Elsa's AnalyticsService fans out to providers implementing AnalyticsProvider
(setup/sendEvent/setUserId/setUserProperties/removeUserProperty). The
adapter is ~15 lines:
class PilotPMProvider implements AnalyticsProvider {
final _sdk = PilotPMEngage.instance;
@override
Future<void> setup() => _sdk.configure(PilotPMConfiguration(apiKey: '…'), store: …);
@override
void sendEvent(String name, {Map<String, dynamic>? params}) =>
_sdk.track(name, properties: params);
@override
void setUserId(String userId) => _sdk.identify(userId);
@override
void setUserProperties(Map<String, dynamic> props) => _sdk.setUserAttributes(props);
@override
void removeUserProperty(String key) => _sdk.setUserAttributes({key: null});
}
Register it next to BrazeProvider and dual-write; validate parity on the PilotPM
dashboard, then retire Braze.
Develop
cd sdk/flutter
dart pub get
dart analyze
dart test
Libraries
- pilotpm_engage
- PilotPM Engage in-app SDK for Dart/Flutter.