analysis options and lint cleanup (#183)

diff --git a/analysis_options.yaml b/analysis_options.yaml
index 572dd23..8f13782 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1 +1,30 @@
 include: package:lints/recommended.yaml
+
+analyzer:
+  language:
+    strict-casts: true
+    strict-inference: true
+    strict-raw-types: true
+
+linter:
+  rules:
+    - always_declare_return_types
+    - avoid_unused_constructor_parameters
+    - cancel_subscriptions
+    - directives_ordering
+    - lines_longer_than_80_chars
+    - literal_only_boolean_expressions
+    - missing_whitespace_between_adjacent_strings
+    - no_adjacent_strings_in_list
+    - no_runtimeType_toString
+    - omit_local_variable_types
+    - package_api_docs
+    - prefer_relative_imports
+    - prefer_single_quotes
+    - test_types_in_equals
+    - throw_in_finally
+    - type_annotate_public_apis
+    - unawaited_futures
+    - unnecessary_await_in_return
+    - unnecessary_lambdas
+    - use_super_parameters
diff --git a/example/ga.dart b/example/ga.dart
index 7d4c645..1fc8491 100644
--- a/example/ga.dart
+++ b/example/ga.dart
@@ -7,7 +7,7 @@
 
 import 'package:usage/usage_io.dart';
 
-void main(List args) async {
+void main(List<String> args) async {
   final defaultUA = 'UA-188575324-1';
 
   if (args.isEmpty) {
@@ -17,7 +17,7 @@
     print('pinging ${args.first}');
   }
 
-  String ua = args.isEmpty ? defaultUA : args.first;
+  var ua = args.isEmpty ? defaultUA : args.first;
 
   Analytics ga = AnalyticsIO(ua, 'ga_test', '3.0');
 
diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart
index 40f2e13..51a2b9a 100644
--- a/lib/src/usage_impl.dart
+++ b/lib/src/usage_impl.dart
@@ -74,7 +74,7 @@
   final ThrottlingBucket _bucket = ThrottlingBucket(20);
   final Map<String, dynamic> _variableMap = {};
 
-  final List<Future> _futures = [];
+  final List<Future<void>> _futures = [];
 
   @override
   AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
@@ -134,13 +134,14 @@
   }
 
   @override
-  Future sendScreenView(String viewName, {Map<String, String>? parameters}) {
+  Future<void> sendScreenView(String viewName,
+      {Map<String, String>? parameters}) {
     var args = <String, String>{'cd': viewName, ...?parameters};
     return _enqueuePayload('screenview', args);
   }
 
   @override
-  Future sendEvent(String category, String action,
+  Future<void> sendEvent(String category, String action,
       {String? label, int? value, Map<String, String>? parameters}) {
     final args = <String, String>{
       'ec': category,
@@ -154,13 +155,13 @@
   }
 
   @override
-  Future sendSocial(String network, String action, String target) {
+  Future<void> sendSocial(String network, String action, String target) {
     var args = <String, String>{'sn': network, 'sa': action, 'st': target};
     return _enqueuePayload('social', args);
   }
 
   @override
-  Future sendTiming(String variableName, int time,
+  Future<void> sendTiming(String variableName, int time,
       {String? category, String? label}) {
     var args = <String, String>{
       'utv': variableName,
@@ -179,7 +180,7 @@
   }
 
   @override
-  Future sendException(String description, {bool? fatal}) {
+  Future<void> 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.
     const maxExceptionLength = 1000;
@@ -234,14 +235,15 @@
   void close() => postHandler.close();
 
   @override
-  String get clientId => properties['clientId'] ??= Uuid().generateV4();
+  String get clientId =>
+      (properties['clientId'] ??= Uuid().generateV4()) as String;
 
   /// Send raw data to analytics. Callers should generally use one of the typed
   /// methods (`sendScreenView`, `sendEvent`, ...).
   ///
   /// Valid values for [hitType] are: 'pageview', 'screenview', 'event',
   /// 'transaction', 'item', 'social', 'exception', and 'timing'.
-  Future sendRaw(String hitType, Map<String, dynamic> args) {
+  Future<void> sendRaw(String hitType, Map<String, dynamic> args) {
     return _enqueuePayload(hitType, args);
   }
 
@@ -258,7 +260,7 @@
     // TODO(sigurdm): Really all the 'send' methods should not return Futures
     // there is not much point in waiting for it. Only [waitForLastPing].
     final completer = Completer<void>();
-    final eventArgs = <String, String>{
+    final eventArgs = <String, dynamic>{
       ...args,
       ..._variableMap,
       'v': '1', // protocol version
@@ -284,7 +286,7 @@
       } else if (!_isSendingScheduled) {
         _isSendingScheduled = true;
         // ignore: unawaited_futures
-        Future.delayed(batchingDelay).then((value) {
+        Future<void>.delayed(batchingDelay).then((value) {
           _isSendingScheduled = false;
           _trySendBatches(completer);
         });
@@ -299,7 +301,7 @@
   static const _maxBytesPerBatch = 16000;
 
   void _trySendBatches(Completer<void> completer) {
-    final futures = <Future>[];
+    final futures = <Future<void>>[];
     while (_batchedEvents.isNotEmpty) {
       final batch = <String>[];
       final totalLength = 0;
@@ -322,7 +324,7 @@
     completer.complete(Future.wait(futures).then((_) {}));
   }
 
-  void _recordFuture(Future f) {
+  void _recordFuture(Future<void> f) {
     _futures.add(f);
     f.whenComplete(() => _futures.remove(f));
   }
@@ -359,8 +361,8 @@
 /// The `Future` from [sendPost] should complete when the operation is finished,
 /// but failures to send the information should be silent.
 abstract class PostHandler {
-  Future sendPost(String url, List<String> batch);
-  String encodeHit(Map<String, String> hit);
+  Future<void> sendPost(String url, List<String> batch);
+  String encodeHit(Map<String, dynamic> hit);
 
   /// Free any used resources.
   void close();
diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart
index 59843b0..cd46c52 100644
--- a/lib/src/usage_impl_html.dart
+++ b/lib/src/usage_impl_html.dart
@@ -55,7 +55,7 @@
   HtmlPostHandler({this.mockRequestor});
 
   @override
-  String encodeHit(Map<String, String> hit) {
+  String encodeHit(Map<String, dynamic> hit) {
     var viewportWidth = document.documentElement!.clientWidth;
     var viewportHeight = document.documentElement!.clientHeight;
     return postEncode({...hit, 'vp': '${viewportWidth}x$viewportHeight'});
@@ -79,12 +79,12 @@
 }
 
 class HtmlPersistentProperties extends PersistentProperties {
-  late final Map _map;
+  late final Map<String, dynamic> _map;
 
   HtmlPersistentProperties(String name) : super(name) {
     var str = window.localStorage[name];
     if (str == null || str.isEmpty) str = '{}';
-    _map = jsonDecode(str);
+    _map = jsonDecode(str) as Map<String, dynamic>;
   }
 
   @override
diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart
index ca6980a..6284dfb 100644
--- a/lib/src/usage_impl_io.dart
+++ b/lib/src/usage_impl_io.dart
@@ -17,14 +17,14 @@
 /// [analyticsUrl] is an optional replacement for the default Google Analytics
 /// URL (`https://www.google-analytics.com/collect`).
 ///
-/// `trackingId`, `applicationName`, and `applicationVersion` values should be supplied.
-/// `analyticsUrl` is optional, and lets user's substitute their own analytics URL for
-/// the default.
+/// `trackingId`, `applicationName`, and `applicationVersion` values should be
+/// supplied. `analyticsUrl` is optional, and lets user's substitute their own
+/// analytics URL for the default.
 ///
 /// `documentDirectory` is where the analytics settings are stored. It
-/// defaults to the user home directory. For regular `dart:io` apps this doesn't need to
-/// be supplied. For Flutter applications, you should pass in a value like
-/// `PathProvider.getApplicationDocumentsDirectory()`.
+/// defaults to the user home directory. For regular `dart:io` apps this doesn't
+/// need to be supplied. For Flutter applications, you should pass in a value
+/// like `PathProvider.getApplicationDocumentsDirectory()`.
 ///
 /// [batchingDelay] is used to control batching behaviour. Events will be sent
 /// batches of 20 after the duration is over from when the first message was
@@ -105,18 +105,18 @@
       : _client = (client ?? HttpClient())..userAgent = createUserAgent();
 
   @override
-  String encodeHit(Map<String, String> hit) {
+  String encodeHit(Map<String, dynamic> hit) {
     return postEncode(hit);
   }
 
   @override
-  Future sendPost(String url, List<String> batch) async {
+  Future<void> sendPost(String url, List<String> batch) async {
     var data = batch.join('\n');
     try {
       var req = await _client.postUrl(Uri.parse(url));
       req.write(data);
       var response = await req.close();
-      await response.drain();
+      await response.drain<void>();
     } on Exception {
       // Catch errors that can happen during a request, but that we can't do
       // anything about, e.g. a missing internet connection.
@@ -135,7 +135,7 @@
 
 class IOPersistentProperties extends PersistentProperties {
   late final File _file;
-  late Map _map;
+  late Map<String, dynamic> _map;
 
   IOPersistentProperties(String name, {String? documentDirPath}) : super(name) {
     var fileName = '.${name.replaceAll(' ', '_')}';
@@ -179,7 +179,7 @@
     try {
       var contents = _file.readAsStringSync();
       if (contents.isEmpty) contents = '{}';
-      _map = jsonDecode(contents);
+      _map = jsonDecode(contents) as Map<String, dynamic>;
     } catch (_) {
       _map = {};
     }
diff --git a/lib/usage.dart b/lib/usage.dart
index 1f1188a..addac23 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -72,14 +72,15 @@
   ///
   /// [parameters] can be any analytics key/value pair. Useful
   /// for custom dimensions, etc.
-  Future sendScreenView(String viewName, {Map<String, String>? parameters});
+  Future<void> sendScreenView(String viewName,
+      {Map<String, String>? parameters});
 
   /// 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,
+  Future<void> sendEvent(String category, String action,
       {String? label, int? value, Map<String, String>? parameters});
 
   /// Sends a Social hit to Google Analytics.
@@ -89,13 +90,13 @@
   /// Google Plus when a user clicks the +1 button, the social action is 'plus'.
   /// [target] specifies the target of a
   /// social interaction. This value is typically a URL but can be any text.
-  Future sendSocial(String network, String action, String target);
+  Future<void> sendSocial(String network, String action, String target);
 
   /// Sends a Timing hit to Google Analytics. [variableName] specifies the
   /// variable name of the timing. [time] specifies the user timing value (in
   /// milliseconds). [category] specifies the category of the timing. [label]
   /// specifies the label of the timing.
-  Future sendTiming(String variableName, int time,
+  Future<void> sendTiming(String variableName, int time,
       {String? category, String? label});
 
   /// Start a timer. The time won't be calculated, and the analytics information
@@ -106,7 +107,7 @@
   /// In order to avoid sending any personally identifying information, the
   /// [description] field must not contain the exception message. In addition,
   /// only the first 100 chars of the description will be sent.
-  Future sendException(String description, {bool? fatal});
+  Future<void> sendException(String description, {bool? fatal});
 
   /// Gets a session variable value.
   dynamic getSessionValue(String param);
@@ -136,7 +137,7 @@
   /// allows CLI apps to delay for a short time waiting for GA requests to
   /// complete, and then do something like call `dart:io`'s `exit()` explicitly
   /// themselves (or the [close] method below).
-  Future waitForLastPing({Duration? timeout});
+  Future<void> waitForLastPing({Duration? timeout});
 
   /// Free any used resources.
   ///
@@ -178,7 +179,7 @@
 
   /// Finish the timer, calculate the elapsed time, and send the information to
   /// analytics. Once this is called, any future invocations are no-ops.
-  Future finish() {
+  Future<void> finish() {
     if (_endMillis != null) return Future.value();
 
     _endMillis = DateTime.now().millisecondsSinceEpoch;
@@ -220,14 +221,15 @@
   String get clientId => '00000000-0000-4000-0000-000000000000';
 
   @override
-  Future sendScreenView(String viewName, {Map<String, String>? parameters}) {
+  Future<void> sendScreenView(String viewName,
+      {Map<String, String>? parameters}) {
     parameters ??= <String, String>{};
     parameters['viewName'] = viewName;
     return _log('screenView', parameters);
   }
 
   @override
-  Future sendEvent(String category, String action,
+  Future<void> sendEvent(String category, String action,
       {String? label, int? value, Map<String, String>? parameters}) {
     parameters ??= <String, String>{};
     return _log(
@@ -237,11 +239,11 @@
   }
 
   @override
-  Future sendSocial(String network, String action, String target) =>
+  Future<void> sendSocial(String network, String action, String target) =>
       _log('social', {'network': network, 'action': action, 'target': target});
 
   @override
-  Future sendTiming(String variableName, int time,
+  Future<void> sendTiming(String variableName, int time,
       {String? category, String? label}) {
     return _log('timing', {
       'variableName': variableName,
@@ -258,7 +260,7 @@
   }
 
   @override
-  Future sendException(String description, {bool? fatal}) =>
+  Future<void> sendException(String description, {bool? fatal}) =>
       _log('exception', {'description': description, 'fatal': fatal});
 
   @override
@@ -271,12 +273,12 @@
   Stream<Map<String, dynamic>> get onSend => _sendController.stream;
 
   @override
-  Future waitForLastPing({Duration? timeout}) => Future.value();
+  Future<void> waitForLastPing({Duration? timeout}) => Future.value();
 
   @override
   void close() {}
 
-  Future _log(String hitType, Map m) {
+  Future<void> _log(String hitType, Map<String, dynamic> m) {
     if (logCalls) {
       print('analytics: $hitType $m');
     }
diff --git a/pubspec.yaml b/pubspec.yaml
index e1f1053..4768a89 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -8,7 +8,7 @@
 repository: https://github.com/dart-lang/usage
 
 environment:
-  sdk: '>=2.15.0 <3.0.0'
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
   meta: ^1.7.0
diff --git a/test/hit_types_test.dart b/test/hit_types_test.dart
index ecdce39..a8d8da9 100644
--- a/test/hit_types_test.dart
+++ b/test/hit_types_test.dart
@@ -105,7 +105,7 @@
       var timer =
           mock.startTimer('compile', category: 'Build', label: 'Compile');
 
-      await Future.delayed(Duration(milliseconds: 20));
+      await Future<void>.delayed(Duration(milliseconds: 20));
 
       await timer.finish();
       expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
@@ -117,7 +117,7 @@
       var time = timer.currentElapsedMillis;
       expect(time, greaterThan(10));
 
-      await Future.delayed(Duration(milliseconds: 10));
+      await Future<void>.delayed(Duration(milliseconds: 10));
       expect(timer.currentElapsedMillis, time);
     });
   });
diff --git a/test/src/common.dart b/test/src/common.dart
index b08d89b..aa440e4 100644
--- a/test/src/common.dart
+++ b/test/src/common.dart
@@ -39,10 +39,11 @@
   void close() {}
 
   @override
-  String encodeHit(Map<String, String> hit) => jsonEncode(hit);
+  String encodeHit(Map<String, dynamic> hit) => jsonEncode(hit);
 
   @override
-  Future sendPost(String url, List<String> batch) => Completer().future;
+  Future<void> sendPost(String url, List<String> batch) =>
+      Completer<void>().future;
 }
 
 class MockProperties extends PersistentProperties {
@@ -68,7 +69,7 @@
   List<String> sentValues = [];
 
   @override
-  Future sendPost(String url, List<String> batch) {
+  Future<void> sendPost(String url, List<String> batch) {
     sentValues.addAll(batch);
 
     return Future.value();
@@ -80,5 +81,5 @@
   void close() {}
 
   @override
-  String encodeHit(Map<String, String> hit) => jsonEncode(hit);
+  String encodeHit(Map<String, dynamic> hit) => jsonEncode(hit);
 }
diff --git a/test/usage_impl_test.dart b/test/usage_impl_test.dart
index 8b2739a..50924a8 100644
--- a/test/usage_impl_test.dart
+++ b/test/usage_impl_test.dart
@@ -34,7 +34,7 @@
       expect(bucket.removeDrop(), false);
 
       // TODO: Re-write to use package:fake_async.
-      await Future.delayed(Duration(milliseconds: 1500));
+      await Future<void>.delayed(Duration(milliseconds: 1500));
       expect(bucket.removeDrop(), true);
     });
   });
diff --git a/test/web_test.dart b/test/web_test.dart
index e2cabb2..5e6e891 100644
--- a/test/web_test.dart
+++ b/test/web_test.dart
@@ -62,7 +62,7 @@
 class MockRequestor {
   int sendCount = 0;
 
-  Future<HttpRequest> request(String url, {String? method, sendData}) {
+  Future<HttpRequest> request(String url, {String? method, Object? sendData}) {
     expect(url, isNotEmpty);
     expect(method, isNotEmpty);
     expect(sendData, isNotEmpty);