| This package is intended to be used on Dart and Flutter related tooling only. | |
| It provides APIs to send events to Google Analytics using the Measurement Protocol. | |
| ## Usage | |
| To get started using this package, import at the entrypoint dart file and | |
| initialize with the required parameters. | |
| The example file shows an end-to-end usage guide for using this package and | |
| can be referred to here [unified_analytics_example.dart](example/unified_analytics_example.dart). | |
| **IMPORTANT**: It is best practice to close the http client connection when finished | |
| sending events, otherwise, you may notice that the dart process hangs on exit. The example below | |
| shows how to handle closing the connection via `analytics.close()` method. | |
| [Link to documentation for http client's close method](https://pub.dev/documentation/http/latest/http/Client-class.html) | |
| ## Opting In and Out of Analytics Collection | |
| It will be important for each tool to expose a trivial method to | |
| disabling or enabling analytics collection. Based on how the user interacts | |
| with the tool, this can be done through the CLI, IDE, etc. The tool will | |
| then pass a boolean to an API exposed by the package as shown below. | |
| ```dart | |
| // Begin by initializing the class | |
| final Analytics analytics = Analytics(...); | |
| // The boolean below simulates the user deciding to opt-out | |
| // of Analytics collection | |
| final bool status = false; | |
| // Call the method to pass the boolean | |
| analytics.setTelemetry(status); | |
| ``` | |
| ## Displaying Consent Message to Users | |
| When a user first uses any tool with this package enabled, the tool using | |
| this package will need to ensure that the user has seen the consent message. | |
| The tool using this package should check with the `Analytics` instance | |
| by invoking the `shouldShowMessage` getter. When this getter returns | |
| `true`, this means that the user has not been enrolled into analytics | |
| collection yet. It is at this point that the tool using this package will | |
| invoke the `getConsentMessage` getter to return a string to share with the | |
| user (each tool will have their own method of displaying the message | |
| through cli stdout, popup modal, etc.). Once the message has been shown, | |
| the tool using this package will need to confirm to the `Analytics` instance | |
| that it has shown the message; it is at this point that the user has | |
| officially been onboarded to analytics collection. | |
| ```dart | |
| // Begin by initializing the class near the entrypoint | |
| final Analytics analytics = Analytics(...); | |
| // This conditional should always run; the first time it is run, this | |
| // will return true since the consent message has never been shown | |
| if (analytics.shouldShowMessage) { | |
| // Simulates displaying the message, this will vary from | |
| // client to client; ie. stdout, popup in IDE, etc. | |
| print(analytics.getConsentMessage); | |
| // After receiving confirmation that the message has been | |
| // displayed, invoking the below method will successfully | |
| // onboard the tool into the config file and allow for | |
| // events to be sent on the next creation of the analytics | |
| // instance | |
| analytics.clientShowedMessage(); | |
| } | |
| ``` | |
| ## Checking User Opt-In Status | |
| Some tools may need to know if the user has opted in for Analytics | |
| collection in order to enable additional functionality. The example below | |
| shows how to check the status. | |
| ```dart | |
| // Begin by initializing the class | |
| final Analytics analytics = Analytics(...); | |
| // This getter will return a boolean showing the status; | |
| // print statement used for trivial usage example | |
| print('This user's status: ${analytics.telemetryEnabled}'); // true if opted-in | |
| ``` | |
| ## Checking for New Versions of Consent Message | |
| In the event that the package consent messaging needs has been updated, an | |
| API has been exposed on an instance of `Analytics` that will notify the tool | |
| using this package whether to display the message again. | |
| ```dart | |
| // Begin by initializing the class | |
| // | |
| // This is assuming that the tool has already been onboarded | |
| // and that the user has already seen the previous version of | |
| // the consent message | |
| final Analytics analytics = Analytics(...); | |
| // Much like the first example, if there is a new version of | |
| // the tools message that needs to be shown, use the same work | |
| // workflow | |
| if (analytics.shouldShowMessage) { | |
| // Simulates displaying the message, this will vary from | |
| // client to client; ie. stdout, popup in IDE, etc. | |
| print(analytics.getConsentMessage); | |
| // After receiving confirmation that the message has been | |
| // displayed, invoking the below method will successfully | |
| // onboard the tool into the config file and allow for | |
| // events to be sent on the next creation of the analytics | |
| // instance | |
| analytics.clientShowedMessage(); | |
| } | |
| ``` | |
| It is important to note events will not be sent if there is a new version of | |
| the consent message. | |
| ## Dependency Telemetry & GA4 Parameter Chunking | |
| When transmitting CLI command execution events via | |
| `Event.dartCliCommandExecuted`, the `dart` CLI tool may pass optional project | |
| SDK constraints (`pubspecEnvironmentSdk`), Flutter SDK flags | |
| (`pubspecHasFlutterSdk`), and public dependency lists (`pubspecDependencies`). | |
| To comply with Google Analytics 4 (GA4) string length restrictions (maximum 100 | |
| characters per event parameter), `unified_analytics` deterministically sorts | |
| and shuffles the dependency list using a 32-bit FNV-1a hash salted with the | |
| canonical dependency set before partitioning it across 20 distinct event | |
| parameters (`pubspec_dep_0` through `pubspec_dep_19`). This eliminates | |
| alphabetical bias while preventing ecosystem-wide starvation of specific package | |
| names across projects, ensuring statistically uniform ecosystem sampling. | |
| ### Guidance for CLI Tool Authors | |
| * **Privacy Filtering**: Callers MUST cross-reference all discovered | |
| dependencies against the authoritative `.dart_tool/package_config.json` | |
| file. Only packages explicitly resolved with a hosted source pointing to | |
| `pub.dev` (or approved public registries) should be passed in | |
| `pubspecDependencies`. Path, git, and intra-workspace member dependencies | |
| must be stripped to prevent leaking confidential internal package names. | |
| * **Project-Level Caching**: In `package:unified_analytics`, a `session_id` | |
| rolls over only after 30 minutes of inactivity. When a developer switches | |
| between different package directories in a workspace within that 30-minute | |
| window, relying on session-level deduplication causes later projects to be | |
| under-reported. CLI tools should maintain a local project state cache | |
| (e.g., a 24-hour TTL mapping project root paths to their last-reported | |
| pubspec hash) so dependencies are captured per project directory without | |
| redundantly re-transmitting on every CLI invocation. | |
| ## Developing Within `package:unified_analytics` | |
| ### Adding new data classes | |
| #### User properties | |
| In Google Analytics, new data fields can be collected as user properties | |
| or events. | |
| User properties are key-value pairs that can be used to segment users. For example, | |
| the Flutter channel used. To request that a new user property | |
| be added, file an issue [using this template](https://github.com/dart-lang/tools/issues/new?template=unified_analytics_user_property.yml). | |
| To add a new user property, add a new property to the `UserProperty` class | |
| in the [`user_property.dart` file](./lib/src/user_property.dart). | |
| #### Events | |
| Events are actions that the user, or tool, performs. In Google Analytics, | |
| events can have associated data. This event data is stored | |
| in key-value pairs. | |
| To request new events, or event data, file an issue | |
| [using this template](https://github.com/dart-lang/tools/issues/new?template=unified_analytics_event.yml). | |
| To add a new event, create a new field in the `DashEvent` enum (if necessary) in | |
| the [`enums.dart` file](./lib/src/enums.dart). | |
| Then, add event data, create a new constructor for the `Event` class | |
| in the [`event.dart` file](./lib/src/event.dart). | |
| ### Testing event collection | |
| When contributing to this package, if the developer needs to verify that | |
| events have been sent, the developer should the use development constructor | |
| so that the events being sent are not going into the production instance. | |
| ```dart | |
| final Analytics analytics = Analytics.development(...); | |
| ``` | |
| Reach out to maintainers to get access to the test Google Analytics endpoint. | |
| ## Advanced Usage: Querying Locally Persisted Logs | |
| This package enables tools to persist the events that have been sent | |
| to Google Analytics for logging by default. This can be very helpful if | |
| tools would like to understand the user's activity level across all | |
| related tooling. For example, if querying the locally persisted logs | |
| shows that the user has not been active for N number of days, a tool that | |
| works within an IDE can prompt the user with a survey to understand why their | |
| level of activity has dropped. | |
| The snippet below shows how to invoke the query and a sample response. | |
| ```dart | |
| // Begin by initializing the class | |
| final Analytics analytics = Analytics(...); | |
| // Printing the query results returns json formatted | |
| // string to view; data can also be accessed through | |
| // [LogFileStats] getters | |
| print(analytics.logFileStats()); | |
| ``` | |
| Refer to the `LogFileStats` instance [variables](lib/src/log_handler.dart) for details on the result. | |
| Explanation of the each key above | |
| - startDateTime: the earliest event that was sent | |
| - minsFromStartDateTime: the number of minutes elapsed since the earliest message | |
| - endDateTime: the latest, most recent event that was sent | |
| - minsFromEndDateTime: the number of minutes elapsed since the latest message | |
| - sessionCount: count of sessions; sessions have a minimum time of 30 minutes | |
| - flutterChannelCount: count of flutter channels (can be 0 if developer is a Dart dev only) | |
| - toolCount: count of the Dart and Flutter tools sending analytics | |
| - recordCount: count of the total number of events in the log file | |
| - eventCount: counts each unique event and how many times they occurred in the log file | |
| Note: You can test any changes to the `LogFileStats` during development by running: | |
| ```shell | |
| dart run example/log_stats.dart | |
| ``` |