apprvd_tracker
Tiny event tracker for Apprvd.app.
Events post straight to Apprvd.app and show up on the dashboard in
real time. They power push audiences and tracker KPIs; the charts come from
Google Analytics. Pure Dart, one dependency (http).
Setup
In Apprvd.app: Data sources → Apprvd.app tracker → Connect source, then copy the generated write key (it's write-only, so safe to ship in the binary).
dependencies:
apprvd_tracker: ^0.6.0
import 'package:apprvd_tracker/apprvd_tracker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info_plus/package_info_plus.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final info = await PackageInfo.fromPlatform();
Apprvd.init(
writeKey: 'pk_…',
endpoint: 'https://…/api/ingest', // from the Data sources page
deviceId: yourStableInstallId, // recommended: accurate unique counts
platform: kIsWeb ? 'web' : defaultTargetPlatform == TargetPlatform.iOS ? 'ios' : 'android',
appVersion: info.version, // e.g. '2.4.1'
);
Apprvd.track('app_open');
}
platform ('ios', 'android' or 'web') and appVersion are optional:
the tracker is pure Dart and can't read them itself, so pass what your app
already has (package_info_plus above). With them, Apprvd.app compares each
release with the one before and flags a key event that stopped arriving on
the new version, such as sign-ups that broke in 2.4.1. Each batch also carries
the SDK's own version, Apprvd.sdkVersion.
Apprvd.identify(user.id); // after sign-in
Apprvd.track('lesson_completed', {'lesson': 'intro', 'score': 87});
await Apprvd.flush(); // e.g. when backgrounded
Flushing when a Flutter app is backgrounded:
class FlushOnPause extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) Apprvd.flush();
}
}
// in main(): WidgetsBinding.instance.addObserver(FlushOnPause());
Experiments
Run an A/B test without Firebase Remote Config. Your code holds the variants; Apprvd.app decides which one each device gets and counts the results.
// The app knows both versions; Apprvd only says which one this device gets.
final copy = Apprvd.variant('paywall_copy', fallback: 'long');
Widget paywall() => switch (copy) {
'short' => const ShortPaywall(), // the version being tested
_ => const LongPaywall(), // 'long', and the fallback if anything fails
};
Apprvd.track('trial_start'); // the goal event the test is measured on
- Create the test in Apprvd.app: Start a change → Test first → Apprvd SDK,
with the same key and variant names. The first variant is the control: pass
it as
fallback. - Apprvd sends only which tests are live, their variant names and weights, and each decided test's winner, never values or results.
- Start, stop and ship from Apprvd.app, with no app update. A decided test serves its winner to everyone; a key with no live test returns the fallback.
- The same device always gets the same variant: a stable hash of the device
id and the test, so pass a stable
deviceId. - One answer per session. The first answer for a key holds until the next launch, so a screen never flips while someone is on it.
- Exposures. The first assigned answer in a session sends an
apprvd_exposureevent (fallbacks and winners don't). Results count the devices exposed to each variant that send the goal event afterwards, with no Remote Config parameter and no Google Analytics custom dimension.
The config is fetched at init. Save it, so a returning user gets their variant
from the first frame. On a first launch, wait briefly for it:
final prefs = await SharedPreferences.getInstance();
Apprvd.init(
writeKey: 'pk_…',
endpoint: 'https://…/api/ingest',
deviceId: yourStableInstallId,
experimentsCache: prefs.getString('apprvd_experiments'),
onExperiments: (json) => prefs.setString('apprvd_experiments', json),
);
await Apprvd.experimentsReady; // done on the first answer, or after 2 s
A key asked for before any config arrived gets its fallback for that session
and isn't counted. configEndpoint: overrides where the config comes from (it
defaults to sdk/config next to the ingest URL).
Rules of the road
- Event names: letters, digits, underscores, max 60 chars (
lesson_completed). - Params must stay under 8 KB as JSON, and don't put personal data in them.
- Events batch in memory (20 per flush, every 10 s) and retry on network errors. The queue caps at 500 and does not survive an app restart.
- Without a stable
deviceId/identify, unique counts inflate: each launch looks like a new device.
Libraries
- apprvd_tracker
- Tiny first-party event tracker for Apprvd.app.