| 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 |
| ``` |