From 11d88b4c60f165a34b6bcc374048f76dc13704d5 Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:33:09 -0400 Subject: [PATCH 1/8] refact: move MpAnalyticsUser to root folder --- lib/src/{models => }/mp_analytics_user.dart | 0 test/src/models/mp_analytics_user_test.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/src/{models => }/mp_analytics_user.dart (100%) diff --git a/lib/src/models/mp_analytics_user.dart b/lib/src/mp_analytics_user.dart similarity index 100% rename from lib/src/models/mp_analytics_user.dart rename to lib/src/mp_analytics_user.dart diff --git a/test/src/models/mp_analytics_user_test.dart b/test/src/models/mp_analytics_user_test.dart index 21c40f1..28d9262 100644 --- a/test/src/models/mp_analytics_user_test.dart +++ b/test/src/models/mp_analytics_user_test.dart @@ -1,4 +1,4 @@ -import 'package:dart_mp_analytics/src/models/mp_analytics_user.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:test/test.dart'; void main() { From 3d777e9c426065f63c535fdf109f7ae288f73e63 Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:33:26 -0400 Subject: [PATCH 2/8] feat: update MPAnalytics initialization --- lib/src/mp_analytics.dart | 101 ++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/lib/src/mp_analytics.dart b/lib/src/mp_analytics.dart index f315a70..c3a1e01 100644 --- a/lib/src/mp_analytics.dart +++ b/lib/src/mp_analytics.dart @@ -3,11 +3,12 @@ import 'dart:convert'; import 'package:dart_mp_analytics/src/core/event_validator.dart'; import 'package:dart_mp_analytics/src/models/mp_analytics_options.dart'; -import 'package:dart_mp_analytics/src/models/mp_analytics_user.dart'; import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:dart_mp_analytics/src/services/default_metadata_service.dart'; import 'package:dart_mp_analytics/src/services/metadata_service.dart'; import 'package:logger/logger.dart'; +import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; /// {@template mp_analytics} @@ -41,14 +42,7 @@ class MPAnalytics { this.debugAnalytics = false, this.enabled = true, this.verbose = false, - Logger? logger, - MPAnalyticsClient? client, - }) { - _initialize( - logger: logger, - client: client, - ); - } + }); /// The options used to configure the [MPAnalytics] instance. This must be an /// instance of correct [MPAnalyticsOptions] subclass for the platform you are @@ -72,29 +66,50 @@ class MPAnalytics { /// Whether or not to log debug messages to the console. final bool verbose; - /// The user associated with this [MPAnalytics] instance. late final MPAnalyticsUser _user; - /// The client used to send requests to the Google Analytics Measurement late final MPAnalyticsClient _client; - /// The session ID for this [MPAnalytics] instance. - String? _sessionId; - late final Logger _logger; - final EventValidator _validator = EventValidator(); + late final EventValidator _validator; + + late final DateTime _sessionStartTime; + + bool _isInitialized = false; - void _initialize({ + /// Whether or not the [MPAnalytics] instance has been initialized. + bool get isInitialized => _isInitialized; + + String? _sessionId; + Timer? _sessionTimer; + + /// Initializes the [MPAnalytics] instance with mock objects for testing. + /// This method is only meant to be used for testing purposes and should not + /// be used in production code. + @visibleForTesting + void initializeMock({ + MPAnalyticsUser? user, MPAnalyticsClient? client, - Logger? logger, + EventValidator? validator, }) { - _logger = logger ?? - Logger( - printer: PrettyPrinter( - methodCount: 0, - ), - ); + _logger = Logger(printer: PrettyPrinter(methodCount: 0)); + + if (!enabled) { + return; + } + + _user = user ?? MPAnalyticsUser(); + _client = client ?? MPAnalyticsClient(urlParameters: options.urlParameters); + _validator = EventValidator(); + _sessionStartTime = DateTime.now().toUtc(); + + _isInitialized = true; + } + + /// Initializes the [MPAnalytics] instance. + void initialize() { + _logger = Logger(printer: PrettyPrinter(methodCount: 0)); if (!enabled) { _verboseLog('MPAnalytics disabled, not initializing'); @@ -103,10 +118,10 @@ class MPAnalytics { _verboseLog('Initializing MPAnalytics'); _user = MPAnalyticsUser(); - _client = client ?? - MPAnalyticsClient( - urlParameters: options.urlParameters, - ); + _client = MPAnalyticsClient(urlParameters: options.urlParameters); + _validator = EventValidator(); + _sessionStartTime = DateTime.now().toUtc(); + _isInitialized = true; _verboseLog('MPAnalytics initialized'); } @@ -115,10 +130,12 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not creating session ID'); return ''; } + if (_sessionId == null) { _sessionId = const Uuid().v4(); _verboseLog('Created new session ID'); - Timer(const Duration(minutes: 30), () { + _sessionTimer?.cancel(); + _sessionTimer = Timer(const Duration(minutes: 30), () { _sessionId = null; _verboseLog('Session ID expired'); }); @@ -135,6 +152,9 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not setting user ID: $id'); return; } + + _verifyInitialized(); + final isValid = _user.setId(id); if (!isValid) { @@ -152,6 +172,9 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not clearing user ID'); return; } + + _verifyInitialized(); + _user.clearId(); _verboseLog('Cleared user ID'); } @@ -167,6 +190,9 @@ class MPAnalytics { ); return; } + + _verifyInitialized(); + final isValid = _user.setProperty(key, value); if (!isValid) { @@ -185,6 +211,8 @@ class MPAnalytics { _verboseLog('MPAnalytics disabled, not removing user property: $key'); return; } + + _verifyInitialized(); _user.removeProperty(key); _verboseLog('Removed user property: $key'); } @@ -206,6 +234,7 @@ class MPAnalytics { return; } + _verifyInitialized(); final (isValidEventName, isValidEventParameter) = ( _validator.validateEventName(name), _validator.validateEventParameters(parameters), @@ -220,12 +249,15 @@ class MPAnalytics { for (final service in metadataServiceList) ...await service.getMetadata(), }; + final engagementTime = + DateTime.now().toUtc().difference(_sessionStartTime).inMilliseconds; + final eventData = { 'name': name, 'params': { // Required parameters to show up in Firebase Analytics // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?hl=pt-br&client_type=gtag#recommended_parameters_for_reports - 'engagement_time_msec': '100', + 'engagement_time_msec': engagementTime, 'session_id': _getOrCreateSessionId(), ...metadata, ...parameters, @@ -246,8 +278,15 @@ class MPAnalytics { } void _verboseLog(String message) { - if (verbose) { - _logger.log(Level.info, message); + if (verbose) _logger.log(Level.info, message); + } + + void _verifyInitialized() { + if (!isInitialized) { + throw StateError( + 'MPAnalytics instance must be initialized first. Make sure to call the ' + 'initialize method before calling any other methods.', + ); } } } From 6393b4ae091498bf7345d901568d2b7a4fb9b665 Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:33:40 -0400 Subject: [PATCH 3/8] feat: update MPAnalytics tests --- ...9384a97db4862b8ab8db.cache.dill.track.dill | Bin 3081296 -> 3078472 bytes coverage/lcov.info | 288 +++++++---- test/src/mp_analytics_test.dart | 477 +++++++++--------- 3 files changed, 433 insertions(+), 332 deletions(-) diff --git a/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill b/build/test_cache/build/c075001b96339384a97db4862b8ab8db.cache.dill.track.dill index 7d36ebe04428e612217ec8834952545bdb3fc0a8..d59175925e124d0d7025bc4a37e2aee947e16dbe 100644 GIT binary patch delta 33117 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 metadataServiceList; late bool debugAnalytics; late bool enabled; - late bool verbose; - late Logger logger; late MPAnalyticsClient client; + late MPAnalyticsUser user; - setUpAll(() { - registerFallbackValue(Level.info); - }); - - setUp(() { - options = MockMPAnalyticsOptions(); - metadataServiceList = [MockMetadataService()]; - debugAnalytics = false; - enabled = true; - verbose = true; - logger = MockLogger(); - client = MockMPAnalyticsClient(); - - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: enabled, - verbose: verbose, - logger: logger, - client: client, - ); - }); - - test('setUserId sets the user ID', () { - const userId = 'user123'; - - analytics.setUserId(userId); - - verify(() => logger.log(Level.info, 'Set user ID: $userId')).called(1); - }); - - test('clearUserId clears the user ID', () { - analytics.clearUserId(); - - verify(() => logger.log(Level.info, 'Cleared user ID')).called(1); - }); - - test('setUserProperty sets the user property', () { - const key = 'propertyKey'; - const value = 'propertyValue'; - - analytics.setUserProperty(key, value); - - verify(() => logger.log(Level.info, 'Set user property: $key = $value')) - .called(1); - }); - - test('removeUserProperty removes the user property', () { - const key = 'propertyKey'; - - analytics.removeUserProperty(key); - - verify(() => logger.log(Level.info, 'Removed user property: $key')) - .called(1); - }); - - test('logEvent logs an event', () async { - const eventName = 'event'; - const eventParameters = {'param1': 'value1', 'param2': 'value2'}; - const optionsBodyParameters = {'bodyKey': 'bodyValue'}; - const metadata = {'metadataKey': 'metadataValue'}; - - when(() => options.bodyParameters).thenReturn(optionsBodyParameters); - when(() => metadataServiceList[0].getMetadata()) - .thenAnswer((_) async => metadata); - when(() => client.logEvent(any(), debug: any(named: 'debug'))) - .thenAnswer((_) async => null); - - await analytics.logEvent(eventName, parameters: eventParameters); - - verify( - () => logger.log( - Level.info, - 'Logging event: $eventName with parameters: $eventParameters', - ), - ).called(1); - verify(() => metadataServiceList[0].getMetadata()).called(1); - verify( - () => client.logEvent( - any(), - debug: debugAnalytics, - ), - ).called(1); - verify(() => logger.log(Level.info, 'Response: null')).called(1); - }); - - test('logEvent does not log event when disabled', () async { - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, - ); - - const eventName = 'event'; - const parameters = {'param1': 'value1', 'param2': 'value2'}; - - await analytics.logEvent(eventName, parameters: parameters); - - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not logging event: $eventName', - ), - ).called(1); - verifyNever(() => metadataServiceList[0].getMetadata()); - verifyNever(() => client.logEvent(any(), debug: any(named: 'debug'))) - .called(0); - }); - - test( - 'MPAnalytics use default client when not provided', - () async { - const eventName = 'event'; - const eventParameters = {'param1': 'value1', 'param2': 'value2'}; - const optionsBodyParameters = {'bodyKey': 'bodyValue'}; - const optionsUrlParameters = {'urlKey': 'urlValue'}; - const metadata = {'metadataKey': 'metadataValue'}; - - when(() => options.bodyParameters).thenReturn(optionsBodyParameters); - when(() => options.urlParameters).thenReturn(optionsUrlParameters); - when(() => metadataServiceList[0].getMetadata()) - .thenAnswer((_) async => metadata); - when(() => client.logEvent(any(), debug: any(named: 'debug'))) - .thenAnswer((_) async => null); + setUp( + () { + options = MockMPAnalyticsOptions(); + metadataServiceList = [MockMetadataService()]; + debugAnalytics = false; + enabled = true; + client = MockMPAnalyticsClient(); + user = MockMPAnalyticsUser(); analytics = MPAnalytics( options: options, metadataServiceList: metadataServiceList, debugAnalytics: debugAnalytics, enabled: enabled, - verbose: verbose, - logger: logger, + )..initializeMock( + user: user, + client: client, + ); + }, + ); + + group( + 'initialization', + () { + test( + 'initializes the MPAnalytics instance', + () { + when(() => options.bodyParameters).thenReturn({}); + when(() => options.urlParameters).thenReturn({}); + + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: enabled, + )..initialize(); + + expect(analytics.isInitialized, isTrue); + }, ); - await analytics.logEvent(eventName, parameters: eventParameters); + test( + 'does not initialize the MPAnalytics instance when disabled', + () { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initialize(); + + expect(analytics.isInitialized, isFalse); + }, + ); - verifyNever( - () => client.logEvent( - any(), - debug: debugAnalytics, - ), + test( + 'throws a StateError when trying to use the MPAnalytics before ' + 'initializing', + () { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: enabled, + ); + + expect( + () => analytics.logEvent('event'), + throwsA(isA()), + ); + }, ); }, ); - test( - 'removeUserProperty do not remove property when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + group( + 'User', + () { + test( + 'setUserId sets the user ID', + () { + const userId = 'user123'; - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + when(() => user.setId(any())).thenReturn(true); + + analytics.setUserId(userId); + + verify(() => user.setId(userId)).called(1); + }, ); - const key = 'propertyKey'; + test( + 'clearUserId clears the user ID', + () { + analytics.clearUserId(); + + verify(() => user.clearId()).called(1); + }, + ); - analytics.removeUserProperty(key); + test( + 'setUserProperty sets the user property', + () { + const key = 'propertyKey'; + const value = 'propertyValue'; - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not removing user property: $key', - ), - ).called(1); - }, - ); + when(() => user.setProperty(any(), any())).thenReturn(true); - test( - 'setUserProperty do not set property when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + analytics.setUserProperty(key, value); - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + verify(() => user.setProperty(key, value)).called(1); + }, ); - const key = 'propertyKey'; - const value = 'propertyValue'; + test( + 'removeUserProperty removes the user property', + () { + const key = 'propertyKey'; - analytics.setUserProperty(key, value); + analytics.removeUserProperty(key); - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not setting user property: {$key : $value}', - ), - ).called(1); - }, - ); + verify(() => user.removeProperty(key)).called(1); + }, + ); - test( - 'setUserId do not set user ID when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + test( + 'removeUserProperty do not remove property when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); - analytics = MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, + const key = 'propertyKey'; + + analytics.removeUserProperty(key); + + verifyNever(() => user.removeProperty(key)); + }, ); - const userId = 'userId'; + test( + 'setUserProperty do not set property when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const key = 'propertyKey'; + const value = 'propertyValue'; + + analytics.setUserProperty(key, value); + + verifyNever(() => user.setProperty(key, value)); + }, + ); + + test( + 'setUserId do not set user ID when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const userId = 'userId'; - analytics.setUserId(userId); + analytics.setUserId(userId); - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not setting user ID: $userId', - ), - ).called(1); + verifyNever(() => user.setId(userId)); + }, + ); + + test( + 'clearUserId do not clear user ID when disabled', + () async { + MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + ) + ..initializeMock(user: user, client: client) + ..clearUserId(); + + verifyNever(() => user.clearId()); + }, + ); }, ); - test( - 'clearUserId do not clear user ID when disabled', - () async { - when(() => logger.log(any(), any())).thenReturn(null); + group( + 'logEvent', + () { + test( + 'logs an event', + () async { + const eventName = 'event'; + const eventParameters = {'param1': 'value1', 'param2': 'value2'}; + const optionsBodyParameters = {'bodyKey': 'bodyValue'}; + const metadata = {'metadataKey': 'metadataValue'}; + const userData = {'userIdKey': 'userIdValue'}; + + when(() => user.data).thenReturn(userData); + when(() => options.bodyParameters).thenReturn(optionsBodyParameters); + when(() => metadataServiceList[0].getMetadata()) + .thenAnswer((_) async => metadata); + when(() => client.logEvent(any(), debug: any(named: 'debug'))) + .thenAnswer((_) async => null); + + await analytics.logEvent(eventName, parameters: eventParameters); + + verify(() => metadataServiceList[0].getMetadata()).called(1); + verify(() => user.data).called(1); + verify( + () => client.logEvent( + any(), + debug: debugAnalytics, + ), + ).called(1); + }, + ); - MPAnalytics( - options: options, - metadataServiceList: metadataServiceList, - debugAnalytics: debugAnalytics, - enabled: false, - verbose: verbose, - logger: logger, - client: client, - ).clearUserId(); - - verify( - () => logger.log( - Level.info, - 'MPAnalytics disabled, not clearing user ID', - ), - ).called(1); + test( + 'does not log event when disabled', + () async { + analytics = MPAnalytics( + options: options, + metadataServiceList: metadataServiceList, + debugAnalytics: debugAnalytics, + enabled: false, + )..initializeMock(user: user, client: client); + + const eventName = 'event'; + const parameters = {'param1': 'value1', 'param2': 'value2'}; + + await analytics.logEvent(eventName, parameters: parameters); + + verifyNever(() => metadataServiceList[0].getMetadata()); + verifyNever(() => user.data).called(0); + verifyNever(() => client.logEvent(any(), debug: any(named: 'debug'))) + .called(0); + }, + ); + + test( + 'throws a AssertionError if try to log invalid event', + () { + const invalidEventName = 'invalid event'; + const validParameters = {'param1': 'value1', 'param2': 'value2'}; + + expect( + () => analytics.logEvent( + invalidEventName, + parameters: validParameters, + ), + throwsA(isA()), + ); + + const validEventName = 'valid_event'; + final invalidParameters = { + 'param1': 'value1', + 'param2': 'value2', + 'invalid': 'a' * 101, + }; + + expect( + () => analytics.logEvent( + validEventName, + parameters: invalidParameters, + ), + throwsA(isA()), + ); + }, + ); }, ); } From 4bcabea8e49f98f7bd453c8a40e6575e6a17f3f2 Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:39:38 -0400 Subject: [PATCH 4/8] refact: adjust package classes export --- lib/dart_mp_analytics.dart | 1 - test/src/mp_analytics_client_test.dart | 2 +- test/src/mp_analytics_test.dart | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dart_mp_analytics.dart b/lib/dart_mp_analytics.dart index 2041412..c9a8755 100644 --- a/lib/dart_mp_analytics.dart +++ b/lib/dart_mp_analytics.dart @@ -5,6 +5,5 @@ export 'src/models/mp_analytics_options.dart'; export 'src/models/mp_analytics_options_mobile.dart'; export 'src/models/mp_analytics_options_web.dart'; export 'src/mp_analytics.dart'; -export 'src/mp_analytics_client.dart'; export 'src/services/default_metadata_service.dart'; export 'src/services/metadata_service.dart'; diff --git a/test/src/mp_analytics_client_test.dart b/test/src/mp_analytics_client_test.dart index 991af22..7836bf8 100644 --- a/test/src/mp_analytics_client_test.dart +++ b/test/src/mp_analytics_client_test.dart @@ -1,4 +1,4 @@ -import 'package:dart_mp_analytics/dart_mp_analytics.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; diff --git a/test/src/mp_analytics_test.dart b/test/src/mp_analytics_test.dart index 9a7a586..ec20272 100644 --- a/test/src/mp_analytics_test.dart +++ b/test/src/mp_analytics_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: inference_failure_on_function_invocation import 'package:dart_mp_analytics/dart_mp_analytics.dart'; +import 'package:dart_mp_analytics/src/mp_analytics_client.dart'; import 'package:dart_mp_analytics/src/mp_analytics_user.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; From 04dc8071c15fc98ee58f9a7e72f5c6418d0c0b02 Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:42:15 -0400 Subject: [PATCH 5/8] feat: update example usage --- example/{ => lib}/main.dart | 6 +- example/pubspec.lock | 188 ++++++++++++++++++++++++++++++++++++ example/pubspec.yaml | 11 +++ 3 files changed, 204 insertions(+), 1 deletion(-) rename example/{ => lib}/main.dart (95%) create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml diff --git a/example/main.dart b/example/lib/main.dart similarity index 95% rename from example/main.dart rename to example/lib/main.dart index 6487535..c7edc6a 100644 --- a/example/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:dart_mp_analytics/dart_mp_analytics.dart'; void main() async { @@ -13,7 +15,7 @@ void main() async { options: options, debugAnalytics: true, // Enable debug mode for testing verbose: true, // Enable verbose logging - ); + )..initialize(); // Log an event await analytics.logEvent( @@ -41,4 +43,6 @@ void main() async { analytics ..clearUserId() ..removeUserProperty('membership'); + + exit(0); } diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..b18cc4b --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,188 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dart_mp_analytics: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.1.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + logger: + dependency: transitive + description: + name: logger + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform_info: + dependency: transitive + description: + name: platform_info + sha256: "61f3bd25642d6624b3d3111892b517cdb17c37a1837189fee346fcc6dd09b052" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..adfd9d6 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,11 @@ +name: example +description: An example of how to use the dart_mp_analytics package. +version: 0.0.1 +publish_to: "none" + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + dart_mp_analytics: + path: ../ From 1cc3d053f4a81272519e323d3fe7b665a9696d9e Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 14:54:14 -0400 Subject: [PATCH 6/8] refact: remove visibility from concrete MpAnalytics Options --- example/lib/main.dart | 3 ++- lib/dart_mp_analytics.dart | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index c7edc6a..a1afd48 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,8 @@ import 'package:dart_mp_analytics/dart_mp_analytics.dart'; void main() async { // Initialize MPAnalytics options - const options = MPAnalyticsOptionsWeb( + // or use MPAnalyticsOptions.mobileStream() + const options = MPAnalyticsOptions.webStream( clientId: 'your_client_id', measurementId: 'your_measurement_id', apiSecret: 'your_api_secret', diff --git a/lib/dart_mp_analytics.dart b/lib/dart_mp_analytics.dart index c9a8755..0639212 100644 --- a/lib/dart_mp_analytics.dart +++ b/lib/dart_mp_analytics.dart @@ -2,8 +2,6 @@ library dart_mp_analytics; export 'src/models/mp_analytics_options.dart'; -export 'src/models/mp_analytics_options_mobile.dart'; -export 'src/models/mp_analytics_options_web.dart'; export 'src/mp_analytics.dart'; export 'src/services/default_metadata_service.dart'; export 'src/services/metadata_service.dart'; From 2ce8a6a9974dc9e89052675a6ec5d3cc37a5aa0a Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 15:21:29 -0400 Subject: [PATCH 7/8] build: update dependencies --- pubspec.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 958adbb..e05ac76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,13 +8,13 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - http: ">=0.13.4 <2.0.0" - logger: ^2.0.2+1 - meta: ^1.3.0 - mocktail: ^1.0.2 + http: ">=1.1.0 <2.0.0" + logger: ^2.2.0 + meta: ^1.15.0 + mocktail: ^1.0.3 platform_info: ^4.0.2 - uuid: ^4.3.1 + uuid: ^4.4.0 dev_dependencies: - test: ^1.21.0 + test: ^1.25.5 very_good_analysis: ^5.1.0 From 6744aa23603e505f79bb1f1e273078f9ff6f607e Mon Sep 17 00:00:00 2001 From: Alvaro Prado Date: Fri, 10 May 2024 15:29:27 -0400 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 8 ++++++++ pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee12b1..de00334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.0 +- Update dependencies +- [Breaking] Hide `MPAnalyticsOptionsWeb` and `MPAnalyticsOptionsMobile` from public API in favor of `MPAnalyticsOptions` factory constructors. +- [Breaking] Update `MPAnalytics` initialization approach. +- Add validations to ensure that the events and user data are valid. +- Fix `engagement_time_msec` calculation before sending an event. + + ## 0.1.3 - Update dependencies diff --git a/pubspec.yaml b/pubspec.yaml index e05ac76..230c5a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_mp_analytics description: A dart only package for wrapper around the Firebase Measurement Protocol API. -version: 0.1.3 +version: 1.0.0 repository: https://github.com/alvarobcprado/dart_mp_analytics issue_tracker: https://github.com/alvarobcprado/dart_mp_analytics/issues