Merge pull request #113 from dart-lang/3.2.0

prep for publishing 3.2.0
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 545d5cc..a0480da 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -2,6 +2,6 @@
   strong-mode: true
 linter:
   rules:
-    #- annotate_overrides
+    - annotate_overrides
     - empty_constructor_bodies
     - empty_statements
diff --git a/changelog.md b/changelog.md
index 2a74305..ac47b61 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,7 @@
 - expose the `Analytics.applicationName` and `Analytics.applicationVersion`
   properties
 - make it easier for clients to extend the `AnalyticsIO` class
+- allow for custom parameters when sending a screenView
 
 ## 3.1.1
 - make Analytics.clientId available immediately
diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart
index e8e10d2..c0b9ae5 100644
--- a/lib/src/usage_impl.dart
+++ b/lib/src/usage_impl.dart
@@ -60,8 +60,11 @@
   static const String _defaultAnalyticsUrl =
       'https://www.google-analytics.com/collect';
 
+  @override
   final String trackingId;
+  @override
   final String applicationName;
+  @override
   final String applicationVersion;
 
   final PersistentProperties properties;
@@ -72,6 +75,7 @@
 
   final List<Future> _futures = [];
 
+  @override
   AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
 
   String _url;
@@ -91,6 +95,7 @@
 
   bool _firstRun;
 
+  @override
   bool get firstRun {
     if (_firstRun == null) {
       _firstRun = properties['firstRun'] == null;
@@ -103,9 +108,7 @@
     return _firstRun;
   }
 
-  /**
-   * Will analytics data be sent?
-   */
+  @override
   bool get enabled {
     bool optIn = analyticsOpt == AnalyticsOpt.optIn;
     return optIn
@@ -113,13 +116,12 @@
         : properties['enabled'] != false;
   }
 
-  /**
-   * Enable or disable sending of analytics data.
-   */
+  @override
   set enabled(bool value) {
     properties['enabled'] = value;
   }
 
+  @override
   Future sendScreenView(String viewName, {Map<String, String> parameters}) {
     Map<String, dynamic> args = {'cd': viewName};
     if (parameters != null) {
@@ -128,8 +130,9 @@
     return _sendPayload('screenview', args);
   }
 
-  Future sendEvent(String category, String action, {String label, int value,
-      Map<String, String> parameters}) {
+  @override
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters}) {
     Map<String, dynamic> args = {'ec': category, 'ea': action};
     if (label != null) args['el'] = label;
     if (value != null) args['ev'] = value;
@@ -139,11 +142,13 @@
     return _sendPayload('event', args);
   }
 
+  @override
   Future sendSocial(String network, String action, String target) {
     Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
     return _sendPayload('social', args);
   }
 
+  @override
   Future sendTiming(String variableName, int time,
       {String category, String label}) {
     Map<String, dynamic> args = {'utv': variableName, 'utt': time};
@@ -152,12 +157,14 @@
     return _sendPayload('timing', args);
   }
 
+  @override
   AnalyticsTimer startTimer(String variableName,
       {String category, String label}) {
     return new AnalyticsTimer(this, variableName,
         category: category, label: label);
   }
 
+  @override
   Future sendException(String description, {bool fatal}) {
     // We trim exceptions to a max length; google analytics will apply it's own
     // truncation, likely around 150 chars or so.
@@ -181,8 +188,10 @@
     return _sendPayload('exception', args);
   }
 
+  @override
   dynamic getSessionValue(String param) => _variableMap[param];
 
+  @override
   void setSessionValue(String param, dynamic value) {
     if (value == null) {
       _variableMap.remove(param);
@@ -191,8 +200,10 @@
     }
   }
 
+  @override
   Stream<Map<String, dynamic>> get onSend => _sendController.stream;
 
+  @override
   Future waitForLastPing({Duration timeout}) {
     Future f = Future.wait(_futures).catchError((e) => null);
 
diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart
index 6861db7..0072dc3 100644
--- a/lib/src/usage_impl_html.dart
+++ b/lib/src/usage_impl_html.dart
@@ -35,6 +35,7 @@
 
   HtmlPostHandler({Function this.mockRequestor});
 
+  @override
   Future sendPost(String url, Map<String, dynamic> parameters) {
     int viewportWidth = document.documentElement.clientWidth;
     int viewportHeight = document.documentElement.clientHeight;
@@ -59,8 +60,10 @@
     _map = JSON.decode(str);
   }
 
+  @override
   dynamic operator [](String key) => _map[key];
 
+  @override
   void operator []=(String key, dynamic value) {
     if (value == null) {
       _map.remove(key);
@@ -71,5 +74,6 @@
     window.localStorage[name] = JSON.encode(_map);
   }
 
+  @override
   void syncSettings() {}
 }
diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart
index ad87e91..9ad366a 100644
--- a/lib/src/usage_impl_io.dart
+++ b/lib/src/usage_impl_io.dart
@@ -79,6 +79,7 @@
 
   IOPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent();
 
+  @override
   Future sendPost(String url, Map<String, dynamic> parameters) async {
     String data = postEncode(parameters);
 
@@ -120,8 +121,10 @@
     syncSettings();
   }
 
+  @override
   dynamic operator [](String key) => _map[key];
 
+  @override
   void operator []=(String key, dynamic value) {
     if (value == null && !_map.containsKey(key)) return;
     if (_map[key] == value) return;
@@ -137,6 +140,7 @@
     } catch (_) {}
   }
 
+  @override
   void syncSettings() {
     try {
       String contents = _file.readAsStringSync();
diff --git a/lib/usage.dart b/lib/usage.dart
index d516172..52e9f46 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -83,7 +83,7 @@
 
   /**
    * Sends a screen view hit to Google Analytics.
-   * 
+   *
    * [parameters] can be any analytics key/value pair. Useful
    * for custom dimensions, etc.
    */
@@ -92,12 +92,12 @@
   /**
    * Sends an Event hit to Google Analytics. [label] specifies the event label.
    * [value] specifies the event value. Values must be non-negative.
-   * 
+   *
    * [parameters] can be any analytics key/value pair. Useful
    * for custom dimensions, etc.
    */
-  Future sendEvent(String category, String action, {String label, int value,
-      Map<String, String> parameters});
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters});
 
   /**
    * Sends a Social hit to Google Analytics. [network] specifies the social
@@ -225,8 +225,11 @@
  * stand-in for that will never ping the GA server, or as a mock in test code.
  */
 class AnalyticsMock implements Analytics {
+  @override
   String get trackingId => 'UA-0';
+  @override
   String get applicationName => 'mock-app';
+  @override
   String get applicationVersion => '1.0.0';
 
   final bool logCalls;
@@ -243,35 +246,40 @@
    */
   AnalyticsMock([this.logCalls = false]);
 
+  @override
   bool get firstRun => false;
 
+  @override
   AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
 
+  @override
   bool enabled = true;
 
   @override
   String get clientId => '00000000-0000-4000-0000-000000000000';
 
+  @override
   Future sendScreenView(String viewName, {Map<String, String> parameters}) {
     parameters ??= <String, String>{};
     parameters['viewName'] = viewName;
     return _log('screenView', parameters);
   }
 
-  Future sendEvent(String category, String action, {String label, int value,
-      Map<String, String> parameters}) {
+  @override
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters}) {
     parameters ??= <String, String>{};
-    return _log('event', {
-      'category': category,
-      'action': action,
-      'label': label,
-      'value': value
-    }..addAll(parameters));
+    return _log(
+        'event',
+        {'category': category, 'action': action, 'label': label, 'value': value}
+          ..addAll(parameters));
   }
 
+  @override
   Future sendSocial(String network, String action, String target) =>
       _log('social', {'network': network, 'action': action, 'target': target});
 
+  @override
   Future sendTiming(String variableName, int time,
       {String category, String label}) {
     return _log('timing', {
@@ -282,21 +290,27 @@
     });
   }
 
+  @override
   AnalyticsTimer startTimer(String variableName,
       {String category, String label}) {
     return new AnalyticsTimer(this, variableName,
         category: category, label: label);
   }
 
+  @override
   Future sendException(String description, {bool fatal}) =>
       _log('exception', {'description': description, 'fatal': fatal});
 
+  @override
   dynamic getSessionValue(String param) => null;
 
+  @override
   void setSessionValue(String param, dynamic value) {}
 
+  @override
   Stream<Map<String, dynamic>> get onSend => _sendController.stream;
 
+  @override
   Future waitForLastPing({Duration timeout}) => new Future.value();
 
   Future _log(String hitType, Map m) {
diff --git a/readme.md b/readme.md
index c9a92c7..6d216e8 100644
--- a/readme.md
+++ b/readme.md
@@ -112,9 +112,7 @@
 
 ## Contributing
 
-Run the tests with `pub run test`.
-
-Analyze the code with `dartanalyzer lib/*.dart test/*.dart`.
+Test can be run using `pub run test`.
 
 ## Issues and bugs
 
diff --git a/test/src/common.dart b/test/src/common.dart
index 4ce7b17..2ace7eb 100644
--- a/test/src/common.dart
+++ b/test/src/common.dart
@@ -34,18 +34,22 @@
     if (props != null) this.props.addAll(props);
   }
 
+  @override
   dynamic operator [](String key) => props[key];
 
+  @override
   void operator []=(String key, dynamic value) {
     props[key] = value;
   }
 
+  @override
   void syncSettings() {}
 }
 
 class MockPostHandler extends PostHandler {
   List<Map<String, dynamic>> sentValues = [];
 
+  @override
   Future sendPost(String url, Map<String, dynamic> parameters) {
     sentValues.add(parameters);
 
diff --git a/test/usage_impl_io_test.dart b/test/usage_impl_io_test.dart
index bce9d03..8889077 100644
--- a/test/usage_impl_io_test.dart
+++ b/test/usage_impl_io_test.dart
@@ -52,39 +52,52 @@
 }
 
 class MockHttpClient implements HttpClient {
+  @override
   String userAgent;
   int sendCount = 0;
   int writeCount = 0;
   bool closed = false;
+
+  @override
   Future<HttpClientRequest> postUrl(Uri url) {
     return new Future.value(new MockHttpClientRequest(this));
   }
 
+  @override
   noSuchMethod(Invocation invocation) {}
 }
 
 class MockHttpClientRequest implements HttpClientRequest {
   final MockHttpClient client;
+
   MockHttpClientRequest(this.client);
+
+  @override
   void write(Object obj) {
     client.writeCount++;
   }
 
+  @override
   Future<HttpClientResponse> close() {
     client.closed = true;
     return new Future.value(new MockHttpClientResponse(client));
   }
 
+  @override
   noSuchMethod(Invocation invocation) {}
 }
 
 class MockHttpClientResponse implements HttpClientResponse {
   final MockHttpClient client;
+
   MockHttpClientResponse(this.client);
+
+  @override
   Future/*<E>*/ drain/*<E>*/([/*=E*/ futureValue]) {
     client.sendCount++;
     return new Future.value();
   }
 
+  @override
   noSuchMethod(Invocation invocation) {}
 }
diff --git a/test/usage_test.dart b/test/usage_test.dart
index 9d2a4e0..37500a9 100644
--- a/test/usage_test.dart
+++ b/test/usage_test.dart
@@ -16,7 +16,8 @@
       mock.sendScreenView('main');
       mock.sendScreenView('withParameters', parameters: {'cd1': 'custom'});
       mock.sendEvent('files', 'save');
-      mock.sendEvent('eventWithParameters', 'save', parameters: {'cd1': 'custom'});
+      mock.sendEvent('eventWithParameters', 'save',
+          parameters: {'cd1': 'custom'});
       mock.sendSocial('g+', 'plus', 'userid');
       mock.sendTiming('compile', 123);
       mock.startTimer('compile').finish();