Skip to content
Back to archipelago.banuacoder.com

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:

bash
archipelago generate analytics_sdk

The prompts are:

  • appName: the name of your app
  • isForMonorepo: leave this as true

Or use a config file:

json
{
  "appName": "MyApp",
  "isForMonorepo": true
}
bash
archipelago generate analytics_sdk --config analytics_config.json

The brick alone has no vendor. Add one or more vendor bricks next:

Step 2: Understand the Generated Structure ​

text
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/                          # AnalyticsImplPackageModule

Each 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:

dart
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 logout

Define an event by mixing in AnalyticEventMixin:

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

The initializer always puts two interceptors first:

  1. ConsentAnalyticInterceptor drops every event while analytics consent is denied.
  2. ScrubbingAnalyticInterceptor redacts 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 ​

dart
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 ​

dart
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 ​

CustomizationWhere to Change
Add a vendorGenerate a vendor brick, or implement AnalyticVendor and register its key
Enrich or filter eventsA custom AnalyticInterceptor
Send an event to some vendorsRoutableAnalyticEvent.targetVendors
Stricter name limitsExtend SharedEventSanitizer in the vendor

Next Steps ​