Merge pull request #114 from dart-lang/add_close_method

add a close method; prep for publishing 3.3.0
diff --git a/analysis_options.yaml b/analysis_options.yaml
index a0480da..2ef22c9 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -3,5 +3,6 @@
 linter:
   rules:
     - annotate_overrides
+    - directives_ordering
     - empty_constructor_bodies
     - empty_statements
diff --git a/changelog.md b/changelog.md
index 87cde3a..351cb38 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,7 @@
 # Changelog
 
-## unreleased
+## 3.3.0
+- added a `close()` method to the `Analytics` class
 - change our minimum SDK from `1.24.0-dev` to `1.24.0` stable
 
 ## 3.2.0
diff --git a/example/ga.dart b/example/ga.dart
index bf0807e..ad64249 100644
--- a/example/ga.dart
+++ b/example/ga.dart
@@ -28,4 +28,8 @@
   await ga.sendTiming('writeDuration', 123);
   await ga.sendEvent('create', 'consoleapp', label: 'Console App');
   print('pinged ${ua}');
+
+  await ga.waitForLastPing();
+
+  ga.close();
 }
diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart
index c0b9ae5..df529e3 100644
--- a/lib/src/usage_impl.dart
+++ b/lib/src/usage_impl.dart
@@ -215,6 +215,9 @@
   }
 
   @override
+  void close() => postHandler.close();
+
+  @override
   String get clientId => properties['clientId'] ??= new Uuid().generateV4();
 
   /**
@@ -291,4 +294,7 @@
  */
 abstract class PostHandler {
   Future sendPost(String url, Map<String, dynamic> parameters);
+
+  /// Free any used resources.
+  void close();
 }
diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart
index 0072dc3..c15cde6 100644
--- a/lib/src/usage_impl_html.dart
+++ b/lib/src/usage_impl_html.dart
@@ -30,10 +30,13 @@
   }
 }
 
-class HtmlPostHandler extends PostHandler {
-  final Function mockRequestor;
+typedef Future<HttpRequest> HttpRequestor(String url,
+    {String method, sendData});
 
-  HtmlPostHandler({Function this.mockRequestor});
+class HtmlPostHandler extends PostHandler {
+  final HttpRequestor mockRequestor;
+
+  HtmlPostHandler({this.mockRequestor});
 
   @override
   Future sendPost(String url, Map<String, dynamic> parameters) {
@@ -43,12 +46,16 @@
     parameters['vp'] = '${viewportWidth}x$viewportHeight';
 
     String data = postEncode(parameters);
-    var request = mockRequestor == null ? HttpRequest.request : mockRequestor;
-    return request(url, method: 'POST', sendData: data).catchError((e) {
+    HttpRequestor requestor =
+        mockRequestor == null ? HttpRequest.request : mockRequestor;
+    return requestor(url, method: 'POST', sendData: data).catchError((e) {
       // Catch errors that can happen during a request, but that we can't do
       // anything about, e.g. a missing internet connection.
     });
   }
+
+  @override
+  void close() {}
 }
 
 class HtmlPersistentProperties extends PersistentProperties {
diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart
index 9ad366a..6e82d41 100644
--- a/lib/src/usage_impl_io.dart
+++ b/lib/src/usage_impl_io.dart
@@ -77,16 +77,21 @@
   final String _userAgent;
   final HttpClient mockClient;
 
-  IOPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent();
+  HttpClient _client;
+
+  IOPostHandler({this.mockClient}) : _userAgent = _createUserAgent();
 
   @override
   Future sendPost(String url, Map<String, dynamic> parameters) async {
     String data = postEncode(parameters);
 
-    HttpClient client = mockClient != null ? mockClient : new HttpClient();
-    client.userAgent = _userAgent;
+    if (_client == null) {
+      _client = mockClient != null ? mockClient : new HttpClient();
+      _client.userAgent = _userAgent;
+    }
+
     try {
-      HttpClientRequest req = await client.postUrl(Uri.parse(url));
+      HttpClientRequest req = await _client.postUrl(Uri.parse(url));
       req.write(data);
       HttpClientResponse response = await req.close();
       response.drain();
@@ -95,6 +100,9 @@
       // anything about, e.g. a missing internet connection.
     }
   }
+
+  @override
+  void close() => _client?.close();
 }
 
 JsonEncoder _jsonEncoder = new JsonEncoder.withIndent('  ');
diff --git a/lib/usage.dart b/lib/usage.dart
index 52e9f46..e79701c 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -164,9 +164,15 @@
    * users won't want their CLI app to pause at the end of the process waiting
    * for Google analytics requests to complete. This method allows CLI apps to
    * delay for a short time waiting for GA requests to complete, and then do
-   * something like call `exit()` explicitly themselves.
+   * something like call `dart:io`'s `exit()` explicitly themselves (or the
+   * [close] method below).
    */
   Future waitForLastPing({Duration timeout});
+
+  /// Free any used resources.
+  ///
+  /// The [Analytics] instance should not be used after this call.
+  void close();
 }
 
 enum AnalyticsOpt {
@@ -313,6 +319,9 @@
   @override
   Future waitForLastPing({Duration timeout}) => new Future.value();
 
+  @override
+  void close() {}
+
   Future _log(String hitType, Map m) {
     if (logCalls) {
       print('analytics: ${hitType} ${m}');
diff --git a/pubspec.yaml b/pubspec.yaml
index 95d6a44..564ba2e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 3.2.0+1
+version: 3.3.0
 description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
diff --git a/readme.md b/readme.md
index 8e1adae..931f91f 100644
--- a/readme.md
+++ b/readme.md
@@ -52,9 +52,15 @@
 something like:
 
 ```dart
-analytics.waitForLastPing(timeout: new Duration(milliseconds: 500)).then((_) {
-  exit(0);
-});
+await analytics.waitForLastPing(timeout: new Duration(milliseconds: 200));
+analytics.close();
+```
+
+or:
+
+```dart
+await analytics.waitForLastPing(timeout: new Duration(milliseconds: 200));
+exit(0);
 ```
 
 ## Using the API
diff --git a/test/all.dart b/test/all.dart
index 393640a..9f93051 100644
--- a/test/all.dart
+++ b/test/all.dart
@@ -5,15 +5,15 @@
 library usage.all_test;
 
 import 'hit_types_test.dart' as hit_types_test;
-import 'usage_test.dart' as usage_test;
-import 'usage_impl_test.dart' as usage_impl_test;
 import 'usage_impl_io_test.dart' as usage_impl_io_test;
+import 'usage_impl_test.dart' as usage_impl_test;
+import 'usage_test.dart' as usage_test;
 import 'uuid_test.dart' as uuid_test;
 
 void main() {
   hit_types_test.defineTests();
-  usage_test.defineTests();
-  usage_impl_test.defineTests();
   usage_impl_io_test.defineTests();
+  usage_impl_test.defineTests();
+  usage_test.defineTests();
   uuid_test.defineTests();
 }
diff --git a/test/src/common.dart b/test/src/common.dart
index 2ace7eb..7799394 100644
--- a/test/src/common.dart
+++ b/test/src/common.dart
@@ -57,4 +57,7 @@
   }
 
   Map<String, dynamic> get last => sentValues.last;
+
+  @override
+  void close() {}
 }
diff --git a/test/web_test.dart b/test/web_test.dart
index 2ddc853..8d91ae9 100644
--- a/test/web_test.dart
+++ b/test/web_test.dart
@@ -6,6 +6,7 @@
 library usage.web_test;
 
 import 'dart:async';
+import 'dart:html';
 
 import 'package:test/test.dart';
 import 'package:usage/src/usage_impl_html.dart';
@@ -61,7 +62,7 @@
 class MockRequestor {
   int sendCount = 0;
 
-  Future request(String url, {String method, String sendData}) {
+  Future<HttpRequest> request(String url, {String method, sendData}) {
     expect(url, isNotEmpty);
     expect(method, isNotEmpty);
     expect(sendData, isNotEmpty);