Skip to content

analytics_sdk Free

Multi-vendor analytics abstraction with real interceptor chain, per-vendor routing, shared sanitization, consent/opt-out, and vendor self-registration.

Version: 2.0.0

Variables

VariableTypeDefaultDescription
appNamestringThe name of your application
isForMonorepobooleantrueWhether this is being generated as part of a monorepo

Architecture

The analytics system uses a multi-vendor dispatch pattern with an interceptor chain that sits between the caller and all registered vendors:

  • analytics_api — Vendor-agnostic contracts (AnalyticTracker, AnalyticVendor, event interfaces, interceptor chain, test harness)
  • analytics_impl — Orchestrator (AnalyticTrackerImpl) that runs events through the interceptor chain, applies super-properties, enforces consent/opt-out, and dispatches to all registered vendors with error isolation
  • Vendor bricks — Per-vendor implementations that self-register into AnalyticVendorRegistry via their RegisterModule constructor

Vendor Bricks

BrickVendorKey Feature
firebase_analytics_implFirebase AnalyticsEvent/param sanitization (40 char limit)
appsflyer_analytics_implAppsFlyerDirect SDK pass-through
mixpanel_analytics_implMixpanelPeople API + flush support
moengage_analytics_implMoEngageProperties builder pattern

Multi-Select

During flutter_modular_monorepo generation, users select vendors via analyticsVendors (type: array):

? Which analytics vendors do you want? (Use space to select)
❯ ◉ firebase
  ◯ appsflyer
  ◯ mixpanel
  ◯ moengage
  ◯ none

Only selected vendor bricks are generated. The orchestrator dispatches events to all registered vendors.

Generated Structure

infrastructure/
├── analytics_api/                  # Contracts (always generated)
│   └── lib/src/
│       ├── data/                   # AnalyticVendor, TrackData, AnalyticVendorRegistry
│       ├── events/                 # AnalyticEvent + specialised event interfaces
│       ├── tracker/                # AnalyticTracker, AnalyticInterceptor, AnalyticInterceptorChain
│       ├── testing/                # InMemoryAnalyticTracker, RecordingAnalyticVendor
│       └── utils/                  # AnalyticInjectorKey, SharedEventSanitizer
├── analytics_impl/                 # Orchestrator (always generated)
│   └── lib/src/
│       ├── analytic_tracker_impl.dart   # Multi-vendor dispatch + interceptor chain
│       ├── analytics_initializer.dart   # DI bootstrap
│       └── di/                          # Injectable config
└── <vendor>_analytics_impl/        # Per-vendor (only selected)
    └── lib/src/
        ├── <vendor>_analytics_vendor.dart
        └── di/
            ├── injector.dart
            └── register_module.dart

AnalyticTracker API

All 11 methods are available on the AnalyticTracker interface injected via GetIt:

dart
final tracker = getIt<AnalyticTracker>();

// Track a custom event
await tracker.track(LoginEvent(method: 'email'));

// Track a screen view
await tracker.trackScreen('HomeScreen', params: {'source': 'push'});

// Identity
await tracker.setUserId('user_123');
await tracker.setUserProperty('plan', 'pro');
await tracker.setUserProperties({'plan': 'pro', 'country': 'ID'});

// Super-properties (merged into every subsequent event)
await tracker.registerSuperProperties({'app_version': '2.1.0'});

// Consent / opt-out
await tracker.setConsent(granted: true);   // GDPR/CCPA consent signal
await tracker.optOut();                    // Stop tracking immediately
await tracker.optIn();                     // Resume tracking

// Lifecycle
await tracker.flush();   // Flush buffered events (vendors that support it)
await tracker.reset();   // Clear all data — call on logout

Interceptor Chain

Interceptors form a chain that wraps every track call. Each interceptor must call chain.proceed(data) to continue, or return null to drop the event silently.

dart
class LoggingInterceptor extends AnalyticInterceptor {
  @override
  Future<TrackData?> intercept(TrackData data, AnalyticInterceptorChain chain) async {
    debugPrint('[Analytics] ${data.name}: ${data.params}');
    return chain.proceed(data); // must call proceed() or return null to drop
  }
}

An enriching interceptor can modify TrackData before passing it downstream:

dart
class SessionInterceptor extends AnalyticInterceptor {
  final String sessionId;
  SessionInterceptor(this.sessionId);

  @override
  Future<TrackData?> intercept(TrackData data, AnalyticInterceptorChain chain) =>
      chain.proceed(data.withProperties({'session_id': sessionId}));
}

Additional lifecycle hooks available on AnalyticInterceptor (all default to no-op):

HookTriggered by
onSetUserIdtracker.setUserId
onSetUserPropertytracker.setUserProperty
onSetUserPropertiestracker.setUserProperties
onRegisterSuperPropertiestracker.registerSuperProperties
onSetConsenttracker.setConsent
onOptOuttracker.optOut
onOptIntracker.optIn
onFlushtracker.flush
onResettracker.reset
onErrorany vendor throws (informational only — do not re-throw)

Event Types

All event types are interfaces. Implement them alongside AnalyticEvent to unlock vendor-specific behaviour.

AnalyticEvent (base)

dart
class LoginEvent with AnalyticEventMixin implements AnalyticEvent {
  const LoginEvent({required this.method, this.isFirstLogin = false});

  final String method;
  final bool isFirstLogin;

  @override
  String get name => 'login';

  @override
  Map<String, dynamic> toJson() => {
    'method': method,
    'is_first_login': isFirstLogin,
  };
}

RoutableAnalyticEvent — per-vendor routing

Restrict which vendors receive an event by implementing RoutableAnalyticEvent. If targetVendors returns null, the event goes to all active vendors.

dart
class FirebaseOnlyEvent with AnalyticEventMixin
    implements AnalyticEvent, RoutableAnalyticEvent {
  @override
  String get name => 'promo_viewed';
  @override
  Map<String, dynamic> toJson() => {'promo_id': 'SUMMER24'};

  @override
  List<String>? get targetVendors => [
    AnalyticInjectorKey.firebaseImpl,
    AnalyticInjectorKey.moEngageImpl,
  ];
}

ScreenViewEvent

Implement to enable native screen-tracking APIs (e.g. Firebase's logScreenView).

dart
abstract interface class ScreenViewEvent implements AnalyticEvent {
  String get screenName;
  String? get screenClass;
}

TimingEvent

Measure how long operations take (API calls, image loads, etc.).

dart
abstract interface class TimingEvent implements AnalyticEvent {
  String get category;    // e.g. 'network'
  String get timingLabel; // e.g. 'products_endpoint'
  int get durationMs;
}

RevenueEvent

Trigger purchase-specific tracking APIs (e.g. AppsFlyer logPurchase, Firebase logPurchase).

dart
abstract interface class RevenueEvent implements AnalyticEvent {
  double get amount;
  String get currency;      // ISO 4217 (e.g. 'USD', 'IDR')
  String? get transactionId;
}

ErrorEvent

Track errors for correlation with crash tools.

dart
abstract interface class ErrorEvent implements AnalyticEvent {
  String get errorDescription;
  bool get isFatal;
}

Shared Sanitization

SharedEventSanitizer enforces constraints common to all vendors:

  • Replaces non-alphanumeric/underscore characters with _, collapses runs
  • Truncates event/property names to 100 characters
  • Truncates string property values to 500 characters
  • Drops unsupported value types (maps, lists, custom objects)

Vendor-specific sanitizers (e.g. Firebase's 40-character limit) extend SharedEventSanitizer and call super before applying their own rules:

dart
const sanitizer = SharedEventSanitizer();
final safeName = sanitizer.sanitizeName(event.name);
final safeParams = sanitizer.sanitizeProperties(event.toJson());

Vendor Self-Registration

Vendors self-register into AnalyticVendorRegistry from their RegisterModule constructor. No manual editing of analytics_impl is required when adding a new vendor.

dart
// In your vendor's RegisterModule:
@module
abstract class MyVendorRegisterModule {
  MyVendorRegisterModule() {
    AnalyticVendorRegistry.instance.register(AnalyticInjectorKey.myVendorImpl);
  }
  // ... provider methods ...
}

AnalyticsInitializer reads AnalyticVendorRegistry.instance.keys at boot time to discover which named vendor bindings to pull from GetIt — no hardcoded list.

Test Harness

Two test doubles are provided in analytics_api under lib/src/testing/:

InMemoryAnalyticTracker

Drop-in replacement for AnalyticTracker in feature-layer unit tests:

dart
final tracker = InMemoryAnalyticTracker();
getIt.registerSingleton<AnalyticTracker>(tracker);

// ... trigger code under test ...

expect(tracker.trackedEvents.where((e) => e.name == 'purchase'), hasLength(1));
expect(tracker.screenViews, contains('CheckoutScreen'));
expect(tracker.superProperties['app_version'], '2.1.0');
expect(tracker.isOptedOut, isFalse);
expect(tracker.currentUserId, 'user_123');

// Reset between tests
tracker.clear();

RecordingAnalyticVendor

Use when testing AnalyticTrackerImpl directly (infrastructure layer):

dart
final vendor = RecordingAnalyticVendor();
final tracker = AnalyticTrackerImpl(vendors: [vendor]);

await tracker.track(LoginEvent(method: 'email'));

expect(vendor.trackedEvents.first.name, 'login');
expect(vendor.userIds, contains('user_123'));
expect(vendor.flushCount, 1);

vendor.clear(); // reset between tests

In tests that exercise AnalyticVendorRegistry, call AnalyticVendorRegistry.instance.clear() in setUp/tearDown to avoid state leaking between tests.

Key Features

  • Real interceptor chain — Interceptors can enrich, filter, or drop events before they reach vendors
  • Per-vendor routingRoutableAnalyticEvent.targetVendors sends events only to selected vendors
  • Vendor self-registration — Adding a vendor brick requires zero changes to analytics_impl
  • Error isolation — One vendor failure does not block others; errors surface via onError hook
  • Consent & opt-out — First-class GDPR/CCPA consent signal and opt-out gate in the orchestrator
  • Super-properties — Merged into every event automatically after registerSuperProperties
  • Shared sanitization — Common name/value constraints applied before dispatch; vendor subclasses extend for stricter limits
  • Named DI@Named(AnalyticInjectorKey.firebaseImpl) uniquely identifies each vendor binding in GetIt
  • Test harness built-inInMemoryAnalyticTracker and RecordingAnalyticVendor ship with analytics_api

Built by Banua Coder