Flutter
Pure Dart, no native code of its own. iOS and Android.
Install
flutter pub add apparentAndroid release builds need the internet permission (Flutter's template only adds it for debug):
<uses-permission android:name="android.permission.INTERNET"/>Start it
In main(), before runApp:
import 'package:apparent/apparent.dart';
await Apparent.start(app: '<app id>', key: '<write key>', apiUrl: 'https://in.apparent.fyi');No need to wait for it. Add disabled: kDebugMode if debug builds shouldn't send.
Screens
MaterialApp(navigatorObservers: [ApparentNavigatorObserver()], routes: {...});Each page route is sent by its name (/settings/account → settings/account, / → home). Dialogs, sheets and unnamed routes are skipped. With go_router, pass it in GoRouter(observers: [...]). Your own names: ApparentNavigatorObserver(screenName: (route) => …), or Apparent.screen('checkout').
Events and users
Apparent.track('paywall_view', {'source': 'onboarding'});
Apparent.setProperties({'plan': 'pro'}); // on every later event; null removes one
Apparent.identify('user-42'); // after sign-in
Apparent.logout(); // on sign-outMore in Events and users.
Purchases
await Apparent.linkRevenueCat((attrs) => Purchases.setAttributes(attrs));Errors and opting out
- Flutter errors and uncaught Dart errors are sent as
errorevents. Your own handlers (Crashlytics, Sentry) still run. - Native iOS and Android crashes are not caught.
- Errors you catch:
Apparent.captureError(e, stack: s). Apparent.optOut()/Apparent.optIn(), remembered.- Ask for consent first? Start with
optOutByDefault: trueand callApparent.optIn()once they agree. Nothing is collected before that. - Nothing showing up? In a debug build the SDK prints
Apparent: app id or key is wrong — events are not being savedonce when the app id or key doesn't match. Copy both again from Connect. - Simulators and emulators say so themselves (
simulatorin each batch), so they're never counted, even in a release build.
Links, notifications and your own filter
Flutter can't see links or notification taps by itself, so pass them on from the package you already use.
// app_links: the link the app was launched with, then each later one
AppLinks().uriLinkStream.listen(Apparent.trackDeepLink);
// firebase_messaging: a tapped notification
final first = await FirebaseMessaging.instance.getInitialMessage();
if (first != null) Apparent.trackPushOpened(first.data, id: first.messageId);
FirebaseMessaging.onMessageOpenedApp.listen((m) => Apparent.trackPushOpened(m.data, id: m.messageId));- Links are sent as
deep_link_openedwith the link (secrets liketoken=orcode=replaced by[redacted]) and itsutm_source,utm_mediumandutm_campaign. A campaign link that opens the app on its first day counts as where that person came from. Turn it off withtrackDeepLinks: false. - Notifications are sent as
push_openedwith thecampaignandtitleyou put in the notification's data. Never the text people saw. Turn it off withtrackPushOpens: false. - Your own filter.
beforeSendsees every event before it is saved. Return it changed, ornullto drop it:
Apparent.start(
app: '<app id>', key: '<write key>',
beforeSend: (event) {
if (event.name == 'debug_tap') return null;
event.props?.remove('email');
return event;
},
);Privacy
The package has no PrivacyInfo.xcprivacy of its own (its plugins ship theirs), so add Apparent's rows to your app's. See Privacy answers.