Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Created October 25, 2023 12:21
Show Gist options
  • Save hawkkiller/515aca8c55e29d343375281fd58cd153 to your computer and use it in GitHub Desktop.
Save hawkkiller/515aca8c55e29d343375281fd58cd153 to your computer and use it in GitHub Desktop.
Analytics
import 'package:analytics_example/analytics_property.dart';
/// A builder for analytics properties.
///
/// This class is used to build a map of properties
/// that can be sent to an analytics service.
final class AnalyticsBuilder {
AnalyticsBuilder() : properties = [];
/// The properties that have been added to this builder.
final List<AnalyticsProperty> properties;
/// Adds a property to this builder.
void add(AnalyticsProperty property) => properties.add(property);
/// Returns the properties as a map.
///
/// This method should be called after all properties have been added.
Map<String, Object?> toMap() {
final result = <String, Object?>{};
for (final property in properties) {
result[property.name] = property.valueSerializable;
}
return result;
}
}
import 'package:analytics_example/analytics_builder.dart';
import 'package:analytics_example/analytics_property.dart';
abstract base class AnalyticsEvent {
const AnalyticsEvent();
String get name;
/// Builds the properties for this event.
void buildProperties(AnalyticsBuilder builder) {}
}
final class UserLoggedEvent extends AnalyticsEvent {
const UserLoggedEvent({
required this.userId,
required this.paid,
});
final String userId;
final bool paid;
@override
String get name => 'user_logged';
@override
void buildProperties(AnalyticsBuilder builder) {
builder.add(StringAnalyticsProperty('user_id', userId));
builder.add(FlagAnalyticsProperty('paid', paid));
}
}
import 'package:flutter/foundation.dart';
sealed class AnalyticsProperty<T extends Object> {
const AnalyticsProperty(this.name, this.value, {this.meta});
/// The name of this property.
final String name;
/// The value of this property.
final T? value;
/// Additional information about this property.
///
/// This can be used to provide additional context about the property.
final Object? meta;
/// Returns the value of this property in a form that can be serialized to JSON.
///
/// If the value is not serializable, this field should be
/// overridden to return a serializable value.
Object? get valueSerializable => value;
}
final class IntAnalyticsProperty extends AnalyticsProperty<int> {
IntAnalyticsProperty(super.name, super.value, {super.meta});
}
final class DoubleAnalyticsProperty extends AnalyticsProperty<double> {
DoubleAnalyticsProperty(super.name, super.value, {super.meta});
}
final class StringAnalyticsProperty extends AnalyticsProperty<String> {
StringAnalyticsProperty(super.name, super.value, {super.meta});
}
final class FlagAnalyticsProperty extends AnalyticsProperty<bool> {
FlagAnalyticsProperty(super.name, super.value, {super.meta});
@override
Object? get valueSerializable {
if (value == null) return null;
return value! ? 'true' : 'false';
}
}
final class EnumAnalyticsProperty<T extends Object> extends AnalyticsProperty<T> {
EnumAnalyticsProperty(super.name, super.value, {super.meta});
@override
Object? get valueSerializable {
if (value == null) return null;
return describeEnum(value!);
}
}
import 'dart:async';
import 'package:analytics_example/analytics_builder.dart';
import 'package:analytics_example/analytics_event.dart';
import 'package:analytics_example/analytics_property.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/foundation.dart';
abstract base class AnalyticsInterceptor {
const AnalyticsInterceptor();
/// Intercepts an event before it is reported to the analytics service
FutureOr<void> afterReport(
AnalyticsEvent event,
List<AnalyticsProperty> properties,
) {}
}
final class LoggingAnalyticsInterceptor extends AnalyticsInterceptor {
const LoggingAnalyticsInterceptor();
@override
FutureOr<void> afterReport(
AnalyticsEvent event,
List<AnalyticsProperty> properties,
) {
if (kDebugMode) {
print('Event: ${event.name}');
for (final property in properties) {
print(' ${property.name}: ${property.value}');
}
}
}
}
abstract base class AnalyticsReporter {
/// Creates a new analytics reporter.
const AnalyticsReporter({List<AnalyticsInterceptor> interceptors = const []})
: _interceptors = interceptors;
/// The interceptors that will be called before an event is reported.
final List<AnalyticsInterceptor> _interceptors;
/// Reports an event to the analytics service.
@nonVirtual
Future<void> reportEvent(AnalyticsEvent event) async {
final builder = AnalyticsBuilder();
event.buildProperties(builder);
await _reportEvent(event.name, builder.toMap());
for (final interceptor in _interceptors) {
await interceptor.afterReport(event, builder.properties);
}
}
/// Reports an event to the analytics service.
///
/// This method should be implemented by the analytics service.
///
/// For example, if you are using Firebase, you would implement this method
/// to call `FirebaseAnalytics.logEvent`.
Future<void> _reportEvent(String name, Map<String, Object?> properties);
}
final class FirebaseAnalyticsReporter extends AnalyticsReporter {
const FirebaseAnalyticsReporter(this._service, {super.interceptors});
final FirebaseAnalytics _service;
@override
Future<void> _reportEvent(String name, Map<String, Object?> properties) =>
_service.logEvent(
name: name,
parameters: properties,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment