Re-land "[unified_analytics] Send enabled_features as an event parameter rather than a user property (#2007)" (#2066)

diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md
index f29141c..f3fd2c9 100644
--- a/pkgs/unified_analytics/CHANGELOG.md
+++ b/pkgs/unified_analytics/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 8.0.0
+- Send `enabled_features` as an event parameter in all events rather than as a user property.
+
 ## 7.0.2
 - Allow `LogStatsFile` to contain more granular event types (specifically `property_editor` events).
 
diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart
index cc8b4bc..8b9fc11 100644
--- a/pkgs/unified_analytics/lib/src/analytics.dart
+++ b/pkgs/unified_analytics/lib/src/analytics.dart
@@ -339,6 +339,7 @@
   final File _clientIdFile;
   final UserProperty _userProperty;
   final LogHandler _logHandler;
+  final String? _enabledFeatures;
 
   /// Tells the client if they need to show a message to the
   /// user; this will return true if it is the first time the
@@ -406,8 +407,8 @@
               truncateStringToLength(io.Platform.operatingSystemVersion, 36),
           locale: io.Platform.localeName,
           clientIde: clientIde,
-          enabledFeatures: enabledFeatures,
         ),
+        _enabledFeatures = enabledFeatures,
         _configHandler = ConfigHandler(
           homeDirectory: homeDirectory,
           configFile: homeDirectory
@@ -613,6 +614,7 @@
       eventName: event.eventName,
       eventData: event.eventData,
       userProperty: _userProperty,
+      enabledFeatures: _enabledFeatures,
     );
 
     if (_enableAsserts) checkBody(body);
@@ -654,6 +656,7 @@
         eventName: collectionEvent.eventName,
         eventData: collectionEvent.eventData,
         userProperty: _userProperty,
+        enabledFeatures: _enabledFeatures,
       );
 
       _logHandler.save(data: body);
@@ -664,6 +667,7 @@
         clientId: clientId,
         eventName: collectionEvent.eventName,
         eventData: collectionEvent.eventData,
+        enabledFeatures: _enabledFeatures,
         userProperty: _userProperty,
       );
 
@@ -774,6 +778,7 @@
       eventName: event.eventName,
       eventData: event.eventData,
       userProperty: _userProperty,
+      enabledFeatures: _enabledFeatures,
     );
 
     if (_enableAsserts) checkBody(body);
diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart
index 5507f39..7dcf8b6 100644
--- a/pkgs/unified_analytics/lib/src/constants.dart
+++ b/pkgs/unified_analytics/lib/src/constants.dart
@@ -87,7 +87,7 @@
 const String kLogFileName = 'dart-flutter-telemetry.log';
 
 /// The current version of the package, should be in line with pubspec version.
-const String kPackageVersion = '7.0.2';
+const String kPackageVersion = '8.0.0';
 
 /// The minimum length for a session.
 const int kSessionDurationMinutes = 30;
diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart
index ccd2457..c451386 100644
--- a/pkgs/unified_analytics/lib/src/event.dart
+++ b/pkgs/unified_analytics/lib/src/event.dart
@@ -8,15 +8,27 @@
 
 final class Event {
   final DashEvent eventName;
-  final Map<String, Object?> eventData;
+  late final Map<String, Object?> eventData;
+
+  Event._({required this.eventName, required this.eventData}) {
+    if (eventData.containsKey('enabled_features')) {
+      throw ArgumentError.value(
+        eventData,
+        'eventData',
+        'The enabled_features key is reserved and cannot '
+            "be used as part of an event's data.",
+      );
+    }
+  }
 
   /// Event that is emitted whenever a user has opted in
   /// or out of the analytics collection.
   ///
   /// [status] - boolean value where `true` indicates user is opting in.
   Event.analyticsCollectionEnabled({required bool status})
-      : eventName = DashEvent.analyticsCollectionEnabled,
-        eventData = {'status': status};
+      : this._(
+            eventName: DashEvent.analyticsCollectionEnabled,
+            eventData: {'status': status});
 
   /// Event that is emitted when an error occurs within
   /// `package:unified_analytics`, tools that are using this package
@@ -35,12 +47,14 @@
     required String workflow,
     required String error,
     String? description,
-  })  : eventName = DashEvent.analyticsException,
-        eventData = {
-          'workflow': workflow,
-          'error': error,
-          if (description != null) 'description': description,
-        };
+  }) : this._(
+          eventName: DashEvent.analyticsException,
+          eventData: {
+            'workflow': workflow,
+            'error': error,
+            if (description != null) 'description': description,
+          },
+        );
 
   /// This is for various workflows within the flutter tool related
   /// to iOS and macOS workflows.
@@ -54,12 +68,14 @@
     required String workflow,
     required String parameter,
     String? result,
-  })  : eventName = DashEvent.appleUsageEvent,
-        eventData = {
-          'workflow': workflow,
-          'parameter': parameter,
-          if (result != null) 'result': result,
-        };
+  }) : this._(
+          eventName: DashEvent.appleUsageEvent,
+          eventData: {
+            'workflow': workflow,
+            'parameter': parameter,
+            if (result != null) 'result': result,
+          },
+        );
 
   /// Event that is emitted periodically to report the performance of the
   /// analysis server's handling of a specific kind of notification from the
@@ -78,12 +94,14 @@
     required String duration,
     required String latency,
     required String method,
-  })  : eventName = DashEvent.clientNotification,
-        eventData = {
-          'duration': duration,
-          'latency': latency,
-          'method': method,
-        };
+  }) : this._(
+          eventName: DashEvent.clientNotification,
+          eventData: {
+            'duration': duration,
+            'latency': latency,
+            'method': method,
+          },
+        );
 
   /// Event that is emitted periodically to report the performance of the
   /// analysis server's handling of a specific kind of request from the client.
@@ -138,19 +156,21 @@
     String? included,
     String? openWorkspacePaths,
     String? removed,
-  })  : eventName = DashEvent.clientRequest,
-        eventData = {
-          if (added != null) 'added': added,
-          'duration': duration,
-          if (excluded != null) 'excluded': excluded,
-          if (files != null) 'files': files,
-          if (included != null) 'included': included,
-          'latency': latency,
-          'method': method,
-          if (openWorkspacePaths != null)
-            'openWorkspacePaths': openWorkspacePaths,
-          if (removed != null) 'removed': removed,
-        };
+  }) : this._(
+          eventName: DashEvent.clientRequest,
+          eventData: {
+            if (added != null) 'added': added,
+            'duration': duration,
+            if (excluded != null) 'excluded': excluded,
+            if (files != null) 'files': files,
+            if (included != null) 'included': included,
+            'latency': latency,
+            'method': method,
+            if (openWorkspacePaths != null)
+              'openWorkspacePaths': openWorkspacePaths,
+            if (removed != null) 'removed': removed,
+          },
+        );
 
   /// An event that reports when the code size measurement is run
   /// via `--analyze-size`.
@@ -158,10 +178,12 @@
   /// [platform] - string identifier for which platform was run "ios", "apk",
   ///   "aab", etc.
   Event.codeSizeAnalysis({required String platform})
-      : eventName = DashEvent.codeSizeAnalysis,
-        eventData = {
-          'platform': platform,
-        };
+      : this._(
+          eventName: DashEvent.codeSizeAnalysis,
+          eventData: {
+            'platform': platform,
+          },
+        );
 
   /// Event that is emitted periodically to report the number of times a given
   /// command has been executed.
@@ -172,11 +194,13 @@
   Event.commandExecuted({
     required int count,
     required String name,
-  })  : eventName = DashEvent.commandExecuted,
-        eventData = {
-          'count': count,
-          'name': name,
-        };
+  }) : this._(
+          eventName: DashEvent.commandExecuted,
+          eventData: {
+            'count': count,
+            'name': name,
+          },
+        );
 
   /// Event to capture usage values for different flutter commands.
   ///
@@ -228,52 +252,59 @@
     bool? runEnableImpeller,
     String? runIOSInterfaceType,
     bool? runIsTest,
-  })  : eventName = DashEvent.commandUsageValues,
-        eventData = {
-          'workflow': workflow,
-          'commandHasTerminal': commandHasTerminal,
-          if (buildBundleTargetPlatform != null)
-            'buildBundleTargetPlatform': buildBundleTargetPlatform,
-          if (buildBundleIsModule != null)
-            'buildBundleIsModule': buildBundleIsModule,
-          if (buildAarProjectType != null)
-            'buildAarProjectType': buildAarProjectType,
-          if (buildAarTargetPlatform != null)
-            'buildAarTargetPlatform': buildAarTargetPlatform,
-          if (buildApkTargetPlatform != null)
-            'buildApkTargetPlatform': buildApkTargetPlatform,
-          if (buildApkBuildMode != null) 'buildApkBuildMode': buildApkBuildMode,
-          if (buildApkSplitPerAbi != null)
-            'buildApkSplitPerAbi': buildApkSplitPerAbi,
-          if (buildAppBundleTargetPlatform != null)
-            'buildAppBundleTargetPlatform': buildAppBundleTargetPlatform,
-          if (buildAppBundleBuildMode != null)
-            'buildAppBundleBuildMode': buildAppBundleBuildMode,
-          if (createProjectType != null) 'createProjectType': createProjectType,
-          if (createAndroidLanguage != null)
-            'createAndroidLanguage': createAndroidLanguage,
-          if (createIosLanguage != null) 'createIosLanguage': createIosLanguage,
-          if (packagesNumberPlugins != null)
-            'packagesNumberPlugins': packagesNumberPlugins,
-          if (packagesProjectModule != null)
-            'packagesProjectModule': packagesProjectModule,
-          if (packagesAndroidEmbeddingVersion != null)
-            'packagesAndroidEmbeddingVersion': packagesAndroidEmbeddingVersion,
-          if (runIsEmulator != null) 'runIsEmulator': runIsEmulator,
-          if (runTargetName != null) 'runTargetName': runTargetName,
-          if (runTargetOsVersion != null)
-            'runTargetOsVersion': runTargetOsVersion,
-          if (runModeName != null) 'runModeName': runModeName,
-          if (runProjectModule != null) 'runProjectModule': runProjectModule,
-          if (runProjectHostLanguage != null)
-            'runProjectHostLanguage': runProjectHostLanguage,
-          if (runAndroidEmbeddingVersion != null)
-            'runAndroidEmbeddingVersion': runAndroidEmbeddingVersion,
-          if (runEnableImpeller != null) 'runEnableImpeller': runEnableImpeller,
-          if (runIOSInterfaceType != null)
-            'runIOSInterfaceType': runIOSInterfaceType,
-          if (runIsTest != null) 'runIsTest': runIsTest,
-        };
+  }) : this._(
+          eventName: DashEvent.commandUsageValues,
+          eventData: {
+            'workflow': workflow,
+            'commandHasTerminal': commandHasTerminal,
+            if (buildBundleTargetPlatform != null)
+              'buildBundleTargetPlatform': buildBundleTargetPlatform,
+            if (buildBundleIsModule != null)
+              'buildBundleIsModule': buildBundleIsModule,
+            if (buildAarProjectType != null)
+              'buildAarProjectType': buildAarProjectType,
+            if (buildAarTargetPlatform != null)
+              'buildAarTargetPlatform': buildAarTargetPlatform,
+            if (buildApkTargetPlatform != null)
+              'buildApkTargetPlatform': buildApkTargetPlatform,
+            if (buildApkBuildMode != null)
+              'buildApkBuildMode': buildApkBuildMode,
+            if (buildApkSplitPerAbi != null)
+              'buildApkSplitPerAbi': buildApkSplitPerAbi,
+            if (buildAppBundleTargetPlatform != null)
+              'buildAppBundleTargetPlatform': buildAppBundleTargetPlatform,
+            if (buildAppBundleBuildMode != null)
+              'buildAppBundleBuildMode': buildAppBundleBuildMode,
+            if (createProjectType != null)
+              'createProjectType': createProjectType,
+            if (createAndroidLanguage != null)
+              'createAndroidLanguage': createAndroidLanguage,
+            if (createIosLanguage != null)
+              'createIosLanguage': createIosLanguage,
+            if (packagesNumberPlugins != null)
+              'packagesNumberPlugins': packagesNumberPlugins,
+            if (packagesProjectModule != null)
+              'packagesProjectModule': packagesProjectModule,
+            if (packagesAndroidEmbeddingVersion != null)
+              'packagesAndroidEmbeddingVersion':
+                  packagesAndroidEmbeddingVersion,
+            if (runIsEmulator != null) 'runIsEmulator': runIsEmulator,
+            if (runTargetName != null) 'runTargetName': runTargetName,
+            if (runTargetOsVersion != null)
+              'runTargetOsVersion': runTargetOsVersion,
+            if (runModeName != null) 'runModeName': runModeName,
+            if (runProjectModule != null) 'runProjectModule': runProjectModule,
+            if (runProjectHostLanguage != null)
+              'runProjectHostLanguage': runProjectHostLanguage,
+            if (runAndroidEmbeddingVersion != null)
+              'runAndroidEmbeddingVersion': runAndroidEmbeddingVersion,
+            if (runEnableImpeller != null)
+              'runEnableImpeller': runEnableImpeller,
+            if (runIOSInterfaceType != null)
+              'runIOSInterfaceType': runIOSInterfaceType,
+            if (runIsTest != null) 'runIsTest': runIsTest,
+          },
+        );
 
   /// Event that is emitted on shutdown to report the structure of the analysis
   /// contexts created immediately after startup.
@@ -322,20 +353,22 @@
     required int transitiveFileLineCount,
     required int transitiveFileUniqueCount,
     required int transitiveFileUniqueLineCount,
-  })  : eventName = DashEvent.contextStructure,
-        eventData = {
-          'contextsFromBothFiles': contextsFromBothFiles,
-          'contextsFromOptionsFiles': contextsFromOptionsFiles,
-          'contextsFromPackagesFiles': contextsFromPackagesFiles,
-          'contextsWithoutFiles': contextsWithoutFiles,
-          'immediateFileCount': immediateFileCount,
-          'immediateFileLineCount': immediateFileLineCount,
-          'numberOfContexts': numberOfContexts,
-          'transitiveFileCount': transitiveFileCount,
-          'transitiveFileLineCount': transitiveFileLineCount,
-          'transitiveFileUniqueCount': transitiveFileUniqueCount,
-          'transitiveFileUniqueLineCount': transitiveFileUniqueLineCount,
-        };
+  }) : this._(
+          eventName: DashEvent.contextStructure,
+          eventData: {
+            'contextsFromBothFiles': contextsFromBothFiles,
+            'contextsFromOptionsFiles': contextsFromOptionsFiles,
+            'contextsFromPackagesFiles': contextsFromPackagesFiles,
+            'contextsWithoutFiles': contextsWithoutFiles,
+            'immediateFileCount': immediateFileCount,
+            'immediateFileLineCount': immediateFileLineCount,
+            'numberOfContexts': numberOfContexts,
+            'transitiveFileCount': transitiveFileCount,
+            'transitiveFileLineCount': transitiveFileLineCount,
+            'transitiveFileUniqueCount': transitiveFileUniqueCount,
+            'transitiveFileUniqueLineCount': transitiveFileUniqueLineCount,
+          },
+        );
 
   /// Event that is emitted when a Dart CLI command has been executed.
   ///
@@ -349,12 +382,14 @@
     required String name,
     required String enabledExperiments,
     int? exitCode,
-  })  : eventName = DashEvent.dartCliCommandExecuted,
-        eventData = {
-          'name': name,
-          'enabledExperiments': enabledExperiments,
-          if (exitCode != null) 'exitCode': exitCode,
-        };
+  }) : this._(
+          eventName: DashEvent.dartCliCommandExecuted,
+          eventData: {
+            'name': name,
+            'enabledExperiments': enabledExperiments,
+            if (exitCode != null) 'exitCode': exitCode,
+          },
+        );
 
   /// Event that is sent from DevTools for various different actions as
   /// indicated by the [eventCategory].
@@ -388,31 +423,33 @@
     String? ideLaunchedFeature,
     String? isWasm,
     CustomMetrics? additionalMetrics,
-  })  : eventName = DashEvent.devtoolsEvent,
-        eventData = {
-          'screen': screen,
-          'eventCategory': eventCategory,
-          'label': label,
-          'value': value,
+  }) : this._(
+          eventName: DashEvent.devtoolsEvent,
+          eventData: {
+            'screen': screen,
+            'eventCategory': eventCategory,
+            'label': label,
+            'value': value,
 
-          'userInitiatedInteraction': userInitiatedInteraction,
+            'userInitiatedInteraction': userInitiatedInteraction,
 
-          // Optional parameters
-          if (g3Username != null) 'g3Username': g3Username,
-          if (userApp != null) 'userApp': userApp,
-          if (userBuild != null) 'userBuild': userBuild,
-          if (userPlatform != null) 'userPlatform': userPlatform,
-          if (devtoolsPlatform != null) 'devtoolsPlatform': devtoolsPlatform,
-          if (devtoolsChrome != null) 'devtoolsChrome': devtoolsChrome,
-          if (devtoolsVersion != null) 'devtoolsVersion': devtoolsVersion,
-          if (ideLaunched != null) 'ideLaunched': ideLaunched,
-          if (isExternalBuild != null) 'isExternalBuild': isExternalBuild,
-          if (isEmbedded != null) 'isEmbedded': isEmbedded,
-          if (ideLaunchedFeature != null)
-            'ideLaunchedFeature': ideLaunchedFeature,
-          if (isWasm != null) 'isWasm': isWasm,
-          if (additionalMetrics != null) ...additionalMetrics.toMap(),
-        };
+            // Optional parameters
+            if (g3Username != null) 'g3Username': g3Username,
+            if (userApp != null) 'userApp': userApp,
+            if (userBuild != null) 'userBuild': userBuild,
+            if (userPlatform != null) 'userPlatform': userPlatform,
+            if (devtoolsPlatform != null) 'devtoolsPlatform': devtoolsPlatform,
+            if (devtoolsChrome != null) 'devtoolsChrome': devtoolsChrome,
+            if (devtoolsVersion != null) 'devtoolsVersion': devtoolsVersion,
+            if (ideLaunched != null) 'ideLaunched': ideLaunched,
+            if (isExternalBuild != null) 'isExternalBuild': isExternalBuild,
+            if (isEmbedded != null) 'isEmbedded': isEmbedded,
+            if (ideLaunchedFeature != null)
+              'ideLaunchedFeature': ideLaunchedFeature,
+            if (isWasm != null) 'isWasm': isWasm,
+            if (additionalMetrics != null) ...additionalMetrics.toMap(),
+          },
+        );
 
   /// Event that contains the results for a specific doctor validator.
   ///
@@ -435,14 +472,16 @@
     required bool partOfGroupedValidator,
     required int doctorInvocationId,
     String? statusInfo,
-  })  : eventName = DashEvent.doctorValidatorResult,
-        eventData = {
-          'validatorName': validatorName,
-          'result': result,
-          'partOfGroupedValidator': partOfGroupedValidator,
-          'doctorInvocationId': doctorInvocationId,
-          if (statusInfo != null) 'statusInfo': statusInfo,
-        };
+  }) : this._(
+          eventName: DashEvent.doctorValidatorResult,
+          eventData: {
+            'validatorName': validatorName,
+            'result': result,
+            'partOfGroupedValidator': partOfGroupedValidator,
+            'doctorInvocationId': doctorInvocationId,
+            if (statusInfo != null) 'statusInfo': statusInfo,
+          },
+        );
 
   /// Generic event for all dash tools to use when encountering an
   /// exception that we want to log.
@@ -452,11 +491,13 @@
   Event.exception({
     required String exception,
     Map<String, Object?> data = const <String, Object?>{},
-  })  : eventName = DashEvent.exception,
-        eventData = {
-          'exception': exception,
-          ...Map.from(data)..removeWhere((key, value) => value == null),
-        };
+  }) : this._(
+          eventName: DashEvent.exception,
+          eventData: {
+            'exception': exception,
+            ...Map.from(data)..removeWhere((key, value) => value == null),
+          },
+        );
 
   /// Event that is emitted from the flutter tool when a build invocation
   /// has been run by the user.
@@ -479,14 +520,16 @@
     String? command,
     String? settings,
     String? error,
-  })  : eventName = DashEvent.flutterBuildInfo,
-        eventData = {
-          'label': label,
-          'buildType': buildType,
-          if (command != null) 'command': command,
-          if (settings != null) 'settings': settings,
-          if (error != null) 'error': error,
-        };
+  }) : this._(
+          eventName: DashEvent.flutterBuildInfo,
+          eventData: {
+            'label': label,
+            'buildType': buildType,
+            if (command != null) 'command': command,
+            if (settings != null) 'settings': settings,
+            if (error != null) 'error': error,
+          },
+        );
 
   /// Provides information about which flutter command was run
   /// and whether it was successful.
@@ -504,19 +547,23 @@
     required String result,
     required bool commandHasTerminal,
     int? maxRss,
-  })  : eventName = DashEvent.flutterCommandResult,
-        eventData = {
-          'commandPath': commandPath,
-          'result': result,
-          'commandHasTerminal': commandHasTerminal,
-          if (maxRss != null) 'maxRss': maxRss,
-        };
+  }) : this._(
+          eventName: DashEvent.flutterCommandResult,
+          eventData: {
+            'commandPath': commandPath,
+            'result': result,
+            'commandHasTerminal': commandHasTerminal,
+            if (maxRss != null) 'maxRss': maxRss,
+          },
+        );
 
   // TODO: eliasyishak, remove this or replace once we have a generic
   //  timing event that can be used by potentially more than one DashTool
   Event.hotReloadTime({required int timeMs})
-      : eventName = DashEvent.hotReloadTime,
-        eventData = {'timeMs': timeMs};
+      : this._(
+          eventName: DashEvent.hotReloadTime,
+          eventData: {'timeMs': timeMs},
+        );
 
   /// Events to be sent for the Flutter Hot Runner.
   Event.hotRunnerInfo({
@@ -539,37 +586,39 @@
     int? scannedSourcesCount,
     int? reassembleTimeInMs,
     int? reloadVMTimeInMs,
-  })  : eventName = DashEvent.hotRunnerInfo,
-        eventData = {
-          'label': label,
-          'targetPlatform': targetPlatform,
-          'sdkName': sdkName,
-          'emulator': emulator,
-          'fullRestart': fullRestart,
-          if (reason != null) 'reason': reason,
-          if (finalLibraryCount != null) 'finalLibraryCount': finalLibraryCount,
-          if (syncedLibraryCount != null)
-            'syncedLibraryCount': syncedLibraryCount,
-          if (syncedClassesCount != null)
-            'syncedClassesCount': syncedClassesCount,
-          if (syncedProceduresCount != null)
-            'syncedProceduresCount': syncedProceduresCount,
-          if (syncedBytes != null) 'syncedBytes': syncedBytes,
-          if (invalidatedSourcesCount != null)
-            'invalidatedSourcesCount': invalidatedSourcesCount,
-          if (transferTimeInMs != null) 'transferTimeInMs': transferTimeInMs,
-          if (overallTimeInMs != null) 'overallTimeInMs': overallTimeInMs,
-          if (compileTimeInMs != null) 'compileTimeInMs': compileTimeInMs,
-          if (findInvalidatedTimeInMs != null)
-            'findInvalidatedTimeInMs': findInvalidatedTimeInMs,
-          if (scannedSourcesCount != null)
-            'scannedSourcesCount': scannedSourcesCount,
-          if (reassembleTimeInMs != null)
-            'reassembleTimeInMs': reassembleTimeInMs,
-          if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs,
-        };
+  }) : this._(
+          eventName: DashEvent.hotRunnerInfo,
+          eventData: {
+            'label': label,
+            'targetPlatform': targetPlatform,
+            'sdkName': sdkName,
+            'emulator': emulator,
+            'fullRestart': fullRestart,
+            if (reason != null) 'reason': reason,
+            if (finalLibraryCount != null)
+              'finalLibraryCount': finalLibraryCount,
+            if (syncedLibraryCount != null)
+              'syncedLibraryCount': syncedLibraryCount,
+            if (syncedClassesCount != null)
+              'syncedClassesCount': syncedClassesCount,
+            if (syncedProceduresCount != null)
+              'syncedProceduresCount': syncedProceduresCount,
+            if (syncedBytes != null) 'syncedBytes': syncedBytes,
+            if (invalidatedSourcesCount != null)
+              'invalidatedSourcesCount': invalidatedSourcesCount,
+            if (transferTimeInMs != null) 'transferTimeInMs': transferTimeInMs,
+            if (overallTimeInMs != null) 'overallTimeInMs': overallTimeInMs,
+            if (compileTimeInMs != null) 'compileTimeInMs': compileTimeInMs,
+            if (findInvalidatedTimeInMs != null)
+              'findInvalidatedTimeInMs': findInvalidatedTimeInMs,
+            if (scannedSourcesCount != null)
+              'scannedSourcesCount': scannedSourcesCount,
+            if (reassembleTimeInMs != null)
+              'reassembleTimeInMs': reassembleTimeInMs,
+            if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs,
+          },
+        );
 
-  // TODO: eliasyishak, add better dartdocs to explain each param
   /// Event that is emitted periodically to report the number of times each lint
   /// has been enabled.
   ///
@@ -579,11 +628,13 @@
   Event.lintUsageCount({
     required int count,
     required String name,
-  })  : eventName = DashEvent.lintUsageCount,
-        eventData = {
-          'count': count,
-          'name': name,
-        };
+  }) : this._(
+          eventName: DashEvent.lintUsageCount,
+          eventData: {
+            'count': count,
+            'name': name,
+          },
+        );
 
   /// Event that is emitted periodically to report the amount of memory being
   /// used.
@@ -602,12 +653,14 @@
     required int rss,
     int? periodSec,
     double? mbPerSec,
-  })  : eventName = DashEvent.memoryInfo,
-        eventData = {
-          'rss': rss,
-          if (periodSec != null) 'periodSec': periodSec,
-          if (mbPerSec != null) 'mbPerSec': mbPerSec
-        };
+  }) : this._(
+          eventName: DashEvent.memoryInfo,
+          eventData: {
+            'rss': rss,
+            if (periodSec != null) 'periodSec': periodSec,
+            if (mbPerSec != null) 'mbPerSec': mbPerSec
+          },
+        );
 
   /// Event that is emitted periodically to report the performance of plugins
   /// when handling requests.
@@ -623,12 +676,14 @@
     required String duration,
     required String method,
     required String pluginId,
-  })  : eventName = DashEvent.pluginRequest,
-        eventData = {
-          'duration': duration,
-          'method': method,
-          'pluginId': pluginId,
-        };
+  }) : this._(
+          eventName: DashEvent.pluginRequest,
+          eventData: {
+            'duration': duration,
+            'method': method,
+            'pluginId': pluginId,
+          },
+        );
 
   /// Event that is emitted periodically to report the frequency with which a
   /// given plugin has been used.
@@ -644,12 +699,14 @@
     required int count,
     required String enabled,
     required String pluginId,
-  })  : eventName = DashEvent.pluginUse,
-        eventData = {
-          'count': count,
-          'enabled': enabled,
-          'pluginId': pluginId,
-        };
+  }) : this._(
+          eventName: DashEvent.pluginUse,
+          eventData: {
+            'count': count,
+            'enabled': enabled,
+            'pluginId': pluginId,
+          },
+        );
 
   /// Event that is emitted when `pub get` is run.
   ///
@@ -663,12 +720,14 @@
     required String packageName,
     required String version,
     required String dependencyType,
-  })  : eventName = DashEvent.pubGet,
-        eventData = {
-          'packageName': packageName,
-          'version': version,
-          'dependencyType': dependencyType,
-        };
+  }) : this._(
+          eventName: DashEvent.pubGet,
+          eventData: {
+            'packageName': packageName,
+            'version': version,
+            'dependencyType': dependencyType,
+          },
+        );
 
   /// Event that is emitted on shutdown to report information about the whole
   /// session for which the analysis server was running.
@@ -692,14 +751,16 @@
     required int duration,
     required String flags,
     required String parameters,
-  })  : eventName = DashEvent.serverSession,
-        eventData = {
-          'clientId': clientId,
-          'clientVersion': clientVersion,
-          'duration': duration,
-          'flags': flags,
-          'parameters': parameters,
-        };
+  }) : this._(
+          eventName: DashEvent.serverSession,
+          eventData: {
+            'clientId': clientId,
+            'clientVersion': clientVersion,
+            'duration': duration,
+            'flags': flags,
+            'parameters': parameters,
+          },
+        );
 
   /// Event that is emitted periodically to report the number of times the
   /// severity of a diagnostic was changed in the analysis options file.
@@ -711,11 +772,13 @@
   Event.severityAdjustment({
     required String diagnostic,
     required String adjustments,
-  })  : eventName = DashEvent.severityAdjustment,
-        eventData = {
-          'diagnostic': diagnostic,
-          'adjustments': adjustments,
-        };
+  }) : this._(
+          eventName: DashEvent.severityAdjustment,
+          eventData: {
+            'diagnostic': diagnostic,
+            'adjustments': adjustments,
+          },
+        );
 
   /// Event that is emitted by `package:unified_analytics` when
   /// the user takes action when prompted with a survey.
@@ -727,11 +790,13 @@
   Event.surveyAction({
     required String surveyId,
     required String status,
-  })  : eventName = DashEvent.surveyAction,
-        eventData = {
-          'surveyId': surveyId,
-          'status': status,
-        };
+  }) : this._(
+          eventName: DashEvent.surveyAction,
+          eventData: {
+            'surveyId': surveyId,
+            'status': status,
+          },
+        );
 
   /// Event that is emitted by `package:unified_analytics` when the
   /// user has been shown a survey.
@@ -739,10 +804,12 @@
   /// [surveyId] - the unique id for a given survey.
   Event.surveyShown({
     required String surveyId,
-  })  : eventName = DashEvent.surveyShown,
-        eventData = {
-          'surveyId': surveyId,
-        };
+  }) : this._(
+          eventName: DashEvent.surveyShown,
+          eventData: {
+            'surveyId': surveyId,
+          },
+        );
 
   /// Event that records how long a given process takes to complete.
   ///
@@ -763,17 +830,15 @@
     required String variableName,
     required int elapsedMilliseconds,
     String? label,
-  })  : eventName = DashEvent.timing,
-        eventData = {
-          'workflow': workflow,
-          'variableName': variableName,
-          'elapsedMilliseconds': elapsedMilliseconds,
-          if (label != null) 'label': label,
-        };
-
-  /// Private constructor to be used when deserializing JSON into an instance
-  /// of [Event].
-  Event._({required this.eventName, required this.eventData});
+  }) : this._(
+          eventName: DashEvent.timing,
+          eventData: {
+            'workflow': workflow,
+            'variableName': variableName,
+            'elapsedMilliseconds': elapsedMilliseconds,
+            if (label != null) 'label': label,
+          },
+        );
 
   @override
   int get hashCode => Object.hash(eventName, jsonEncode(eventData));
diff --git a/pkgs/unified_analytics/lib/src/user_property.dart b/pkgs/unified_analytics/lib/src/user_property.dart
index f0e177d..a607b07e 100644
--- a/pkgs/unified_analytics/lib/src/user_property.dart
+++ b/pkgs/unified_analytics/lib/src/user_property.dart
@@ -21,7 +21,6 @@
   final String hostOsVersion;
   final String locale;
   final String? clientIde;
-  final String? enabledFeatures;
 
   final File sessionFile;
 
@@ -43,7 +42,6 @@
     required this.hostOsVersion,
     required this.locale,
     required this.clientIde,
-    required this.enabledFeatures,
     required this.sessionFile,
   });
 
@@ -157,6 +155,5 @@
         'host_os_version': hostOsVersion,
         'locale': locale,
         'client_ide': clientIde,
-        'enabled_features': enabledFeatures,
       };
 }
diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart
index 41848a2..182facb 100644
--- a/pkgs/unified_analytics/lib/src/utils.dart
+++ b/pkgs/unified_analytics/lib/src/utils.dart
@@ -76,13 +76,17 @@
   required DashEvent eventName,
   required Map<String, Object?> eventData,
   required UserProperty userProperty,
+  required String? enabledFeatures,
 }) =>
     <String, Object?>{
       'client_id': clientId,
       'events': <Map<String, Object?>>[
         <String, Object?>{
           'name': eventName.label,
-          'params': eventData,
+          'params': {
+            ...eventData,
+            if (enabledFeatures != null) 'enabled_features': enabledFeatures,
+          },
         }
       ],
       'user_properties': userProperty.preparePayload()
diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml
index 6ef6475..65c5de4 100644
--- a/pkgs/unified_analytics/pubspec.yaml
+++ b/pkgs/unified_analytics/pubspec.yaml
@@ -5,7 +5,7 @@
 # LINT.IfChange
 # When updating this, keep the version consistent with the changelog and the
 # value in lib/src/constants.dart.
-version: 7.0.2
+version: 8.0.0
 # LINT.ThenChange(lib/src/constants.dart)
 repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics
 issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics
diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart
index 2e78df4..cd5b942 100644
--- a/pkgs/unified_analytics/test/unified_analytics_test.dart
+++ b/pkgs/unified_analytics/test/unified_analytics_test.dart
@@ -498,7 +498,7 @@
 # All other lines are configuration lines. They have
 # the form "name=value". If multiple lines contain
 # the same configuration name with different values,
-# the parser will default to a conservative value. 
+# the parser will default to a conservative value.
 
 # DISABLING TELEMETRY REPORTING
 #
@@ -548,7 +548,7 @@
 # All other lines are configuration lines. They have
 # the form "name=value". If multiple lines contain
 # the same configuration name with different values,
-# the parser will default to a conservative value. 
+# the parser will default to a conservative value.
 
 # DISABLING TELEMETRY REPORTING
 #
@@ -632,7 +632,7 @@
     );
   });
 
-  test('Check that UserProperty class has all the necessary keys', () {
+  test('The UserProperty class has all the necessary keys', () {
     const userPropertyKeys = <String>[
       'session_id',
       'flutter_channel',
@@ -645,7 +645,6 @@
       'host_os_version',
       'locale',
       'client_ide',
-      'enabled_features',
     ];
     expect(analytics.userPropertyMap.keys.length, userPropertyKeys.length,
         reason: 'There should only be ${userPropertyKeys.length} keys');
@@ -826,6 +825,7 @@
       eventName: DashEvent.hotReloadTime,
       eventData: eventData,
       userProperty: analytics.userProperty,
+      enabledFeatures: 'enable-native-assets',
     );
 
     // Checks for the top level keys
@@ -833,8 +833,6 @@
         reason: '"client_id" is required at the top level');
     expect(body.containsKey('events'), true,
         reason: '"events" is required at the top level');
-    expect(body.containsKey('user_properties'), true,
-        reason: '"user_properties" is required at the top level');
 
     // Regex for the client id
     final clientIdPattern = RegExp(
@@ -846,14 +844,36 @@
     expect(clientIdPattern.hasMatch(body['client_id'] as String), true,
         reason: 'The client id is not properly formatted, ie '
             '46cc0ba6-f604-4fd9-aa2f-8a20beb24cd4');
-    expect(
-        (body['events'][0] as Map<String, dynamic>).containsKey('name'), true,
+    expect(body['events'][0] as Map<String, Object?>, contains('name'),
         reason: 'Each event in the events array needs a name');
-    expect(
-        (body['events'][0] as Map<String, dynamic>).containsKey('params'), true,
+    expect(body['events'][0] as Map<String, Object?>, contains('params'),
         reason: 'Each event in the events array needs a params key');
   });
 
+  test(
+      'The list of enabled features is included as an event parameter in every sent event',
+      () {
+    final eventData = <String, dynamic>{
+      'time': 5,
+      'command': 'run',
+    };
+
+    final Map<String, dynamic> body = generateRequestBody(
+      clientId: Uuid().generateV4(),
+      eventName: DashEvent.hotReloadTime,
+      eventData: eventData,
+      userProperty: analytics.userProperty,
+      enabledFeatures: 'enable-native-assets',
+    );
+
+    expect((body['events'][0] as Map<String, Object?>)['params'],
+        contains('enabled_features'));
+    expect(
+        (body['events'][0] as Map<String, dynamic>)['params']
+            ['enabled_features'],
+        'enable-native-assets');
+  });
+
   test('Check that log file is correctly persisting events sent', () {
     final int numberOfEvents = max((kLogFileLength * 0.1).floor(), 5);