Infrastructure SDKs
How to add multi-vendor analytics to a Flutter app
Free Reference for analytics_sdk: every variable, option and package it generates. Command: archipelago generate analytics_sdk
Generate the Analytics SDK, add vendor bricks, track events, screen views and user properties, and handle consent, opt-out and tests.
What you'll learn
- Generating the Analytics SDK and adding vendor bricks
- Understanding the multi-vendor tracker and its interceptor chain
- Tracking events, screen views and user properties
- Handling consent, opt-out and testing
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
- Credentials for each vendor you plan to use (see the vendor tutorials)
Step 1: Generate the Analytics SDK
Pick vendors at project creation with analyticsVendors and the generator does this for you. To add it later, run:
archipelago generate analytics_sdkThe prompts are:
- appName: the name of your app
- isForMonorepo: leave this as
true
Or use a config file:
{
"appName": "MyApp",
"isForMonorepo": true
}archipelago generate analytics_sdk --config analytics_config.jsonThe brick alone has no vendor. Add one or more vendor bricks next:
Step 2: Understand the Generated Structure
infrastructure/
├── analytics_api/ # Contracts, interceptors, test doubles
│ └── lib/src/
│ ├── data/ # AnalyticVendor, TrackData, AnalyticVendorRegistry
│ ├── events/ # AnalyticEvent and specialised event interfaces
│ ├── tracker/ # AnalyticTracker, AnalyticInterceptor, chain
│ ├── testing/ # InMemoryAnalyticTracker, RecordingAnalyticVendor
│ └── utils/ # AnalyticInjectorKey, SharedEventSanitizer
└── analytics_impl/ # Orchestrator
└── lib/src/
├── analytic_tracker_impl.dart # Dispatch to all vendors
├── analytics_initializer.dart # register() and initialize()
└── di/ # AnalyticsImplPackageModuleEach vendor brick adds its own <vendor>_analytics_impl package. A vendor registers itself in AnalyticVendorRegistry, so analytics_impl never depends on a vendor.
Step 3: Check the Wiring
The brick declares AnalyticsImplPackageModule under archipelago: di_modules in pubspec.yaml. The wiring step adds it to lib/di/injector.dart. Its init() calls AnalyticsInitializer.register(getIt). That registers AnalyticTracker as a lazy singleton in the global GetIt.
The post-launch initializer calls AnalyticsInitializer.initialize(getIt). It starts the vendors only after analytics consent is granted. Until then the tracker holds up to 100 vendor calls and replays them afterwards.
Step 4: Track Events
Features depend on analytics_api only:
final tracker = getIt<AnalyticTracker>();
await tracker.track(LoginEvent(method: 'email'));
await tracker.trackScreen('HomeScreen', params: {'source': 'push'});
await tracker.setUserId('user_123');
await tracker.setUserProperty('plan', 'pro');
await tracker.registerSuperProperties({'app_version': '2.1.0'});
await tracker.reset(); // call on logoutDefine an event by mixing in AnalyticEventMixin:
class LoginEvent with AnalyticEventMixin implements AnalyticEvent {
const LoginEvent({required this.method});
final String method;
@override
String get name => 'login';
@override
Map<String, dynamic> toJson() => {'method': method};
}Implement RoutableAnalyticEvent to send an event to chosen vendors only. Implement ScreenViewEvent, TimingEvent, RevenueEvent or ErrorEvent to unlock vendor-specific APIs.
Step 5: Consent and Privacy
The initializer always puts two interceptors first:
ConsentAnalyticInterceptordrops every event while analytics consent is denied.ScrubbingAnalyticInterceptorredacts personal data from event properties.
Call tracker.optOut() and tracker.optIn() for a user-level switch. Revoking consent calls setCollectionEnabled(false) on every started vendor.
Step 6: Add Your Own Interceptor
class LoggingInterceptor extends AnalyticInterceptor {
@override
Future<TrackData?> intercept(
TrackData data,
AnalyticInterceptorChain chain,
) async {
debugPrint('[Analytics] ${data.name}: ${data.params}');
return chain.proceed(data);
}
}Return null instead of calling chain.proceed to drop the event. Register the interceptor with AnalyticInterceptorRegistry.instance.register().
Step 7: Test with the Built-in Doubles
final tracker = InMemoryAnalyticTracker();
getIt.registerSingleton<AnalyticTracker>(tracker);
// ... run the code under test ...
expect(tracker.trackedEvents.where((e) => e.name == 'purchase'), hasLength(1));Use RecordingAnalyticVendor to test AnalyticTrackerImpl directly. Call AnalyticVendorRegistry.instance.clear() in setUp to avoid shared state.
Key Customization Points
| Customization | Where to Change |
|---|---|
| Add a vendor | Generate a vendor brick, or implement AnalyticVendor and register its key |
| Enrich or filter events | A custom AnalyticInterceptor |
| Send an event to some vendors | RoutableAnalyticEvent.targetVendors |
| Stricter name limits | Extend SharedEventSanitizer in the vendor |