diff --git a/packages/firebase_analytics/firebase_analytics/android/src/main/java/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.java b/packages/firebase_analytics/firebase_analytics/android/src/main/java/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.java index e7d20ea9292a..503522f65a6f 100755 --- a/packages/firebase_analytics/firebase_analytics/android/src/main/java/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.java +++ b/packages/firebase_analytics/firebase_analytics/android/src/main/java/io/flutter/plugins/firebase/analytics/FlutterFirebaseAnalyticsPlugin.java @@ -53,7 +53,8 @@ private static Bundle createBundleFromMap(Map map) { if (value instanceof String) { bundle.putString(key, (String) value); } else if (value instanceof Integer) { - bundle.putInt(key, (Integer) value); + // FirebaseAnalytics default event parameters only support long and double types, so we convert the int to a long. + bundle.putLong(key, (Integer) value); } else if (value instanceof Long) { bundle.putLong(key, (Long) value); } else if (value instanceof Double) { diff --git a/packages/firebase_analytics/firebase_analytics/example/lib/main.dart b/packages/firebase_analytics/firebase_analytics/example/lib/main.dart index 31d823204fc5..0f2f816bf2b5 100755 --- a/packages/firebase_analytics/firebase_analytics/example/lib/main.dart +++ b/packages/firebase_analytics/firebase_analytics/example/lib/main.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'firebase_options.dart'; @@ -68,7 +69,28 @@ class _MyHomePageState extends State { }); } + Future _setDefaultEventParameters() async { + if (kIsWeb) { + setMessage( + '"setDefaultEventParameters()" is not supported on web platform', + ); + } else { + // Only strings, numbers & null (longs & doubles for android, ints and doubles for iOS) are supported for default event parameters: + await widget.analytics.setDefaultEventParameters({ + 'string': 'string', + 'int': 42, + 'long': 12345678910, + 'double': 42.0, + 'bool': true.toString(), + }); + setMessage('setDefaultEventParameters succeeded'); + } + } + Future _sendAnalyticsEvent() async { + // Only strings and numbers (longs & doubles for android, ints and doubles for iOS) are supported for GA custom event parameters: + // https://firebase.google.com/docs/reference/ios/firebaseanalytics/api/reference/Classes/FIRAnalytics#+logeventwithname:parameters: + // https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics#public-void-logevent-string-name,-bundle-params await widget.analytics.logEvent( name: 'test_event', parameters: { @@ -79,9 +101,9 @@ class _MyHomePageState extends State { // Only strings and numbers (ints & doubles) are supported for GA custom event parameters: // https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets#overview 'bool': true.toString(), - 'items': [itemCreator()] }, ); + setMessage('logEvent succeeded'); } @@ -327,6 +349,10 @@ class _MyHomePageState extends State { onPressed: _testResetAnalyticsData, child: const Text('Test resetAnalyticsData'), ), + MaterialButton( + onPressed: _setDefaultEventParameters, + child: const Text('Test setDefaultEventParameters'), + ), Text( _message, style: const TextStyle(color: Color.fromARGB(255, 0, 155, 0)), diff --git a/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart b/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart index 621e8cbae759..1c5250297adf 100755 --- a/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart +++ b/packages/firebase_analytics/firebase_analytics/lib/src/firebase_analytics.dart @@ -81,30 +81,14 @@ class FirebaseAnalytics extends FirebasePluginPlatform { Map? parameters, AnalyticsCallOptions? callOptions, }) async { - if (_reservedEventNames.contains(name)) { - throw ArgumentError.value( - name, - 'name', - 'Event name is reserved and cannot be used', - ); - } - - const String kReservedPrefix = 'firebase_'; + _logEventNameValidation(name); - if (name.startsWith(kReservedPrefix)) { - throw ArgumentError.value( - name, - 'name', - 'Prefix "$kReservedPrefix" is reserved and cannot be used.', + parameters?.forEach((key, value) { + assert( + value is String || value is num, + "'string' OR 'number' must be set as the value of the parameter: $key", ); - } - - if (parameters?['items'] is List) { - // ignore: cast_nullable_to_non_nullable - parameters!['items'] = (parameters['items'] as List) - .map((item) => item.asMap()) - .toList(); - } + }); await _delegate.logEvent( name: name, @@ -128,6 +112,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { Future setDefaultEventParameters( Map? defaultParameters, ) async { + defaultParameters?.forEach((key, value) { + assert( + value is String || value is num || value == null, + "'string', 'null' or 'number' must be set as the value of the parameter: $key", + ); + }); await _delegate.setDefaultEventParameters(defaultParameters); } @@ -243,15 +233,15 @@ class FirebaseAnalytics extends FirebasePluginPlatform { List? items, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'add_payment_info', - parameters: { + parameters: filterOutNulls({ _COUPON: coupon, _CURRENCY: currency, _PAYMENT_TYPE: paymentType, _VALUE: value, - _ITEMS: items, - }, + _ITEMS: _marshalItems(items), + }), callOptions: callOptions, ); } @@ -270,15 +260,15 @@ class FirebaseAnalytics extends FirebasePluginPlatform { List? items, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'add_shipping_info', - parameters: { + parameters: filterOutNulls({ _COUPON: coupon, _CURRENCY: currency, _SHIPPING_TIER: shippingTier, _VALUE: value, - _ITEMS: items, - }, + _ITEMS: _marshalItems(items), + }), callOptions: callOptions, ); } @@ -298,10 +288,10 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'add_to_cart', parameters: filterOutNulls({ - _ITEMS: items, + _ITEMS: _marshalItems(items), _VALUE: value, _CURRENCY: currency, }), @@ -325,10 +315,10 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'add_to_wishlist', parameters: filterOutNulls({ - _ITEMS: items, + _ITEMS: _marshalItems(items), _VALUE: value, _CURRENCY: currency, }), @@ -367,7 +357,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'ecommerce_purchase', parameters: filterOutNulls({ _CURRENCY: currency, @@ -407,7 +397,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'ad_impression', parameters: filterOutNulls({ _AD_PLATFORM: adPlatform, @@ -425,7 +415,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { /// /// See: https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html#APP_OPEN Future logAppOpen({AnalyticsCallOptions? callOptions}) { - return logEvent( + return _delegate.logEvent( name: 'app_open', callOptions: callOptions, ); @@ -447,12 +437,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'begin_checkout', parameters: filterOutNulls({ _VALUE: value, _CURRENCY: currency, - _ITEMS: items, + _ITEMS: _marshalItems(items), _COUPON: coupon, }), callOptions: callOptions, @@ -474,7 +464,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? cp1, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'campaign_details', parameters: filterOutNulls({ _SOURCE: source, @@ -501,7 +491,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required num value, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'earn_virtual_currency', parameters: filterOutNulls({ _VIRTUAL_CURRENCY_NAME: virtualCurrencyName, @@ -535,7 +525,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'present_offer', parameters: filterOutNulls({ _ITEM_ID: itemId, @@ -565,7 +555,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'purchase_refund', parameters: filterOutNulls({ _CURRENCY: currency, @@ -590,7 +580,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'generate_lead', parameters: filterOutNulls({ _CURRENCY: currency, @@ -611,7 +601,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required String groupId, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'join_group', parameters: filterOutNulls({ _GROUP_ID: groupId, @@ -632,7 +622,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? character, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'level_up', parameters: filterOutNulls({ _LEVEL: level, @@ -649,7 +639,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required String levelName, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'level_start', parameters: filterOutNulls({ _LEVEL_NAME: levelName, @@ -666,7 +656,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { int? success, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'level_end', parameters: filterOutNulls({ _LEVEL_NAME: levelName, @@ -682,7 +672,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required int checkoutStep, required String checkoutOption, }) { - return logEvent( + return _delegate.logEvent( name: 'set_checkout_option', parameters: filterOutNulls({ _CHECKOUT_STEP: checkoutStep, @@ -701,7 +691,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? loginMethod, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'login', parameters: filterOutNulls({ _METHOD: loginMethod, @@ -724,7 +714,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? character, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'post_score', parameters: filterOutNulls({ _SCORE: score, @@ -755,13 +745,13 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'purchase', parameters: filterOutNulls({ _CURRENCY: currency, _COUPON: coupon, _VALUE: value, - _ITEMS: items, + _ITEMS: _marshalItems(items), _TAX: tax, _SHIPPING: shipping, _TRANSACTION_ID: transactionId, @@ -784,12 +774,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'remove_from_cart', parameters: filterOutNulls({ _CURRENCY: currency, _VALUE: value, - _ITEMS: items, + _ITEMS: _marshalItems(items), }), callOptions: callOptions, ); @@ -805,7 +795,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? screenName, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'screen_view', parameters: filterOutNulls({ _SCREEN_CLASS: screenClass, @@ -826,12 +816,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { List? items, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'select_item', parameters: filterOutNulls({ _ITEM_LIST_ID: itemListId, _ITEM_LIST_NAME: itemListName, - _ITEMS: items, + _ITEMS: _marshalItems(items), }), callOptions: callOptions, ); @@ -851,12 +841,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? promotionName, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'select_promotion', parameters: filterOutNulls({ _CREATIVE_NAME: creativeName, _CREATIVE_SLOT: creativeSlot, - _ITEMS: items, + _ITEMS: _marshalItems(items), _LOCATION_ID: locationId, _PROMOTION_ID: promotionId, _PROMOTION_NAME: promotionName, @@ -876,12 +866,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { List? items, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'view_cart', parameters: filterOutNulls({ _CURRENCY: currency, _VALUE: value, - _ITEMS: items, + _ITEMS: _marshalItems(items), }), callOptions: callOptions, ); @@ -906,7 +896,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? travelClass, AnalyticsCallOptions? callOptions, }) { - return logEvent( + return _delegate.logEvent( name: 'search', parameters: filterOutNulls( { @@ -937,7 +927,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required String contentType, required String itemId, }) { - return logEvent( + return _delegate.logEvent( name: 'select_content', parameters: filterOutNulls({ _CONTENT_TYPE: contentType, @@ -957,7 +947,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required String itemId, required String method, }) { - return logEvent( + return _delegate.logEvent( name: 'share', parameters: filterOutNulls({ _CONTENT_TYPE: contentType, @@ -978,7 +968,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { Future logSignUp({ required String signUpMethod, }) { - return logEvent( + return _delegate.logEvent( name: 'sign_up', parameters: filterOutNulls({ _METHOD: signUpMethod, @@ -997,7 +987,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { required String virtualCurrencyName, required num value, }) { - return logEvent( + return _delegate.logEvent( name: 'spend_virtual_currency', parameters: filterOutNulls({ _ITEM_NAME: itemName, @@ -1015,7 +1005,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { /// /// See: https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html#TUTORIAL_BEGIN Future logTutorialBegin() { - return logEvent(name: 'tutorial_begin'); + return _delegate.logEvent(name: 'tutorial_begin'); } /// Logs the standard `tutorial_complete` event. @@ -1026,7 +1016,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { /// /// See: https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html#TUTORIAL_COMPLETE Future logTutorialComplete() { - return logEvent(name: 'tutorial_complete'); + return _delegate.logEvent(name: 'tutorial_complete'); } /// Logs the standard `unlock_achievement` event with a given achievement @@ -1041,7 +1031,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { Future logUnlockAchievement({ required String id, }) { - return logEvent( + return _delegate.logEvent( name: 'unlock_achievement', parameters: filterOutNulls({ _ACHIEVEMENT_ID: id, @@ -1066,12 +1056,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { }) { _requireValueAndCurrencyTogether(value, currency); - return logEvent( + return _delegate.logEvent( name: 'view_item', parameters: filterOutNulls({ _CURRENCY: currency, _VALUE: value, - _ITEMS: items, + _ITEMS: _marshalItems(items), }), ); } @@ -1087,10 +1077,10 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? itemListId, String? itemListName, }) { - return logEvent( + return _delegate.logEvent( name: 'view_item_list', parameters: filterOutNulls({ - _ITEMS: items, + _ITEMS: _marshalItems(items), _ITEM_LIST_ID: itemListId, _ITEM_LIST_NAME: itemListName, }), @@ -1110,12 +1100,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? promotionId, String? promotionName, }) { - return logEvent( + return _delegate.logEvent( name: 'view_promotion', parameters: filterOutNulls({ _CREATIVE_NAME: creativeName, _CREATIVE_SLOT: creativeSlot, - _ITEMS: items, + _ITEMS: _marshalItems(items), _LOCATION_ID: locationId, _PROMOTION_ID: promotionId, _PROMOTION_NAME: promotionName, @@ -1132,7 +1122,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { Future logViewSearchResults({ required String searchTerm, }) { - return logEvent( + return _delegate.logEvent( name: 'view_search_results', parameters: filterOutNulls({ _SEARCH_TERM: searchTerm, @@ -1155,7 +1145,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { String? affiliation, List? items, }) { - return logEvent( + return _delegate.logEvent( name: 'refund', parameters: filterOutNulls({ _CURRENCY: currency, @@ -1165,7 +1155,7 @@ class FirebaseAnalytics extends FirebasePluginPlatform { _SHIPPING: shipping, _TRANSACTION_ID: transactionId, _AFFILIATION: affiliation, - _ITEMS: items, + _ITEMS: _marshalItems(items), }), ); } @@ -1218,6 +1208,32 @@ void _requireValueAndCurrencyTogether(double? value, String? currency) { } } +void _logEventNameValidation(String name) { + if (_reservedEventNames.contains(name)) { + throw ArgumentError.value( + name, + 'name', + 'Event name is reserved and cannot be used', + ); + } + + const String kReservedPrefix = 'firebase_'; + + if (name.startsWith(kReservedPrefix)) { + throw ArgumentError.value( + name, + 'name', + 'Prefix "$kReservedPrefix" is reserved and cannot be used.', + ); + } +} + +List>? _marshalItems(List? items) { + if (items == null) return null; + + return items.map((AnalyticsEventItem item) => item.asMap()).toList(); +} + /// Reserved event names that cannot be used. /// /// See: https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event.html diff --git a/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart b/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart index 0497157fe385..c40f4eb6f889 100644 --- a/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart +++ b/tests/integration_test/firebase_analytics/firebase_analytics_e2e_test.dart @@ -62,11 +62,25 @@ void main() { parameters: { 'foo': 'bar', 'baz': 500, - 'items': [analyticsEventItem], }, ), completes, ); + + // test custom event assert exception + await expectLater( + FirebaseAnalytics.instance.logEvent( + name: 'testing-parameters', + parameters: { + 'foo': 'bar', + 'baz': 500, + // Lists are not supported + 'items': [analyticsEventItem], + }, + ), + throwsA(isA()), + ); + // test 2 reserved events await expectLater( FirebaseAnalytics.instance.logAdImpression( @@ -219,6 +233,19 @@ void main() { FirebaseAnalytics.instance.setDefaultEventParameters(null), completes, ); + + // test custom event assert exception + await expectLater( + FirebaseAnalytics.instance.setDefaultEventParameters( + { + 'foo': 'bar', + 'baz': 500, + // Lists are not supported + 'items': ['some', 'items'], + }, + ), + throwsA(isA()), + ); } }, );