Merge branch 'master' into add-docs
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..1603cdd
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+# Dependabot configuration file.
+# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
+version: 2
+
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
new file mode 100644
index 0000000..7632673
--- /dev/null
+++ b/.github/workflows/test-package.yml
@@ -0,0 +1,64 @@
+name: Dart CI
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+  schedule:
+    - cron: "0 0 * * 0"
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+jobs:
+  # Check code formatting and static analysis on a single OS (linux)
+  # against Dart dev.
+  analyze:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [dev]
+    steps:
+      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
+      - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: dart analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  # Run tests on a matrix consisting of two dimensions:
+  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+  # 2. release channel: dev
+  test:
+    needs: analyze
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # Add macos-latest and/or windows-latest if relevant for this package.
+        os: [ubuntu-latest]
+        sdk: [2.19.0, dev]
+    steps:
+      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
+      - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Run VM tests
+        run: dart test --platform vm
+        if: always() && steps.install.outcome == 'success'
+      - name: Run Chrome tests
+        run: dart test --platform chrome
+        if: always() && steps.install.outcome == 'success'
diff --git a/.test_config b/.test_config
deleted file mode 100644
index 352d2fe..0000000
--- a/.test_config
+++ /dev/null
@@ -1,3 +0,0 @@
-{ 
-  "test_package": true
-}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d6a5454..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-language: dart
-
-dart:
-- 2.2.0
-- dev
-
-dart_task:
-- test: --platform vm,chrome
-
-matrix:
-  include:
-  # Only validate formatting using the dev release
-  - dart: dev
-    dart_task: dartfmt
-  - dart: dev
-    dart_task:
-      dartanalyzer: --fatal-infos --fatal-warnings .
-  - dart: 2.2.0
-    dart_task:
-      dartanalyzer: --fatal-warnings .
-
-# Only building master means that we don't run two builds for each pull request.
-branches:
-  only: [master]
-
-cache:
-  directories:
-  - $HOME/.pub-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab7f87a..8932188 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,20 @@
-## 2.1.0-dev
+## 2.1.2-dev
 
-* Require Dart `2.2.0` or later.
+* Require Dart 2.19
 * Add an example.
 
+## 2.1.1
+
+* Require Dart 2.14
+* Populate the pubspec `repository` field.
+* Handle multichannel messages where the ID element is a `double` at runtime
+  instead of an `int`. When reading an array with `dart2wasm` numbers within the
+  array are parsed as `double`.
+
+## 2.1.0
+
+* Stable release for null safety.
+
 ## 2.0.0
 
 **Breaking changes**
diff --git a/LICENSE b/LICENSE
index de31e1a..dbd2843 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
-Copyright 2015, the Dart project authors. All rights reserved.
+Copyright 2015, the Dart project authors. 
+
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
@@ -9,7 +10,7 @@
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.
-    * Neither the name of Google Inc. nor the names of its
+    * Neither the name of Google LLC nor the names of its
       contributors may be used to endorse or promote products derived
       from this software without specific prior written permission.
 
diff --git a/README.md b/README.md
index 2ec970b..ae1ea21 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+[![Dart CI](https://github.com/dart-lang/stream_channel/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/stream_channel/actions/workflows/test-package.yml)
+[![pub package](https://img.shields.io/pub/v/stream_channel.svg)](https://pub.dev/packages/stream_channel)
+[![package publisher](https://img.shields.io/pub/publisher/stream_channel.svg)](https://pub.dev/packages/stream_channel/publisher)
+
 This package exposes the `StreamChannel` interface, which represents a two-way
 communication channel. Each `StreamChannel` exposes a `Stream` for receiving
 data and a `StreamSink` for sending it. 
diff --git a/analysis_options.yaml b/analysis_options.yaml
index b36c874..8e5d4a7 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,17 +1,14 @@
-include: package:pedantic/analysis_options.yaml
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
 analyzer:
-  strong-mode:
-    implicit-casts: false
-  # These are errors when building in Google
-  errors:
-    unused_import: error
-    unused_element: error
-    unused_local_variable: error
-    dead_code: error
+  language:
+    strict-casts: true
 
 linter:
   rules:
-    - annotate_overrides
     - comment_references
-    - prefer_generic_function_type_aliases
-    - prefer_typing_uninitialized_variables
+    - always_declare_return_types
+    - omit_local_variable_types
+    - prefer_single_quotes
+    - unawaited_futures
+    - use_super_parameters
diff --git a/lib/src/close_guarantee_channel.dart b/lib/src/close_guarantee_channel.dart
index 60916d0..13432d1 100644
--- a/lib/src/close_guarantee_channel.dart
+++ b/lib/src/close_guarantee_channel.dart
@@ -12,18 +12,18 @@
 /// that closing the sink causes the stream to close before it emits any more
 /// events
 ///
-/// This is exposed via [new StreamChannel.withCloseGuarantee].
+/// This is exposed via [StreamChannel.withCloseGuarantee].
 class CloseGuaranteeChannel<T> extends StreamChannelMixin<T> {
   @override
   Stream<T> get stream => _stream;
-  _CloseGuaranteeStream<T> _stream;
+  late final _CloseGuaranteeStream<T> _stream;
 
   @override
   StreamSink<T> get sink => _sink;
-  _CloseGuaranteeSink<T> _sink;
+  late final _CloseGuaranteeSink<T> _sink;
 
   /// The subscription to the inner stream.
-  StreamSubscription<T> _subscription;
+  StreamSubscription<T>? _subscription;
 
   /// Whether the sink has closed, causing the underlying channel to disconnect.
   bool _disconnected = false;
@@ -48,8 +48,8 @@
   _CloseGuaranteeStream(this._inner, this._channel);
 
   @override
-  StreamSubscription<T> listen(void Function(T) onData,
-      {Function onError, void Function() onDone, bool cancelOnError}) {
+  StreamSubscription<T> listen(void Function(T)? onData,
+      {Function? onError, void Function()? onDone, bool? cancelOnError}) {
     // If the channel is already disconnected, we shouldn't dispatch anything
     // but a done event.
     if (_channel._disconnected) {
@@ -74,16 +74,17 @@
   /// The [CloseGuaranteeChannel] this belongs to.
   final CloseGuaranteeChannel<T> _channel;
 
-  _CloseGuaranteeSink(StreamSink<T> inner, this._channel) : super(inner);
+  _CloseGuaranteeSink(super.inner, this._channel);
 
   @override
   Future<void> close() {
     var done = super.close();
     _channel._disconnected = true;
-    if (_channel._subscription != null) {
+    var subscription = _channel._subscription;
+    if (subscription != null) {
       // Don't dispatch anything but a done event.
-      _channel._subscription.onData(null);
-      _channel._subscription.onError(null);
+      subscription.onData(null);
+      subscription.onError(null);
     }
     return done;
   }
diff --git a/lib/src/disconnector.dart b/lib/src/disconnector.dart
index e883537..61969cb 100644
--- a/lib/src/disconnector.dart
+++ b/lib/src/disconnector.dart
@@ -74,11 +74,11 @@
 
   /// The subscription to the stream passed to [addStream], if a stream is
   /// currently being added.
-  StreamSubscription<T> _addStreamSubscription;
+  StreamSubscription<T>? _addStreamSubscription;
 
   /// The completer for the future returned by [addStream], if a stream is
   /// currently being added.
-  Completer _addStreamCompleter;
+  Completer? _addStreamCompleter;
 
   /// Whether we're currently adding a stream with [addStream].
   bool get _inAddStream => _addStreamSubscription != null;
@@ -97,7 +97,7 @@
   }
 
   @override
-  void addError(error, [StackTrace stackTrace]) {
+  void addError(Object error, [StackTrace? stackTrace]) {
     if (_closed) throw StateError('Cannot add event after closing.');
     if (_inAddStream) {
       throw StateError('Cannot add event while adding stream.');
@@ -117,8 +117,8 @@
 
     _addStreamCompleter = Completer.sync();
     _addStreamSubscription = stream.listen(_inner.add,
-        onError: _inner.addError, onDone: _addStreamCompleter.complete);
-    return _addStreamCompleter.future.then((_) {
+        onError: _inner.addError, onDone: _addStreamCompleter!.complete);
+    return _addStreamCompleter!.future.then((_) {
       _addStreamCompleter = null;
       _addStreamSubscription = null;
     });
@@ -143,7 +143,7 @@
     var future = _inner.close();
 
     if (_inAddStream) {
-      _addStreamCompleter.complete(_addStreamSubscription.cancel());
+      _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
       _addStreamCompleter = null;
       _addStreamSubscription = null;
     }
diff --git a/lib/src/guarantee_channel.dart b/lib/src/guarantee_channel.dart
index cfee99e..2aa8b7b 100644
--- a/lib/src/guarantee_channel.dart
+++ b/lib/src/guarantee_channel.dart
@@ -10,24 +10,24 @@
 
 /// A [StreamChannel] that enforces the stream channel guarantees.
 ///
-/// This is exposed via [new StreamChannel.withGuarantees].
+/// This is exposed via [StreamChannel.withGuarantees].
 class GuaranteeChannel<T> extends StreamChannelMixin<T> {
   @override
   Stream<T> get stream => _streamController.stream;
 
   @override
   StreamSink<T> get sink => _sink;
-  _GuaranteeSink<T> _sink;
+  late final _GuaranteeSink<T> _sink;
 
   /// The controller for [stream].
   ///
   /// This intermediate controller allows us to continue listening for a done
   /// event even after the user has canceled their subscription, and to send our
   /// own done event when the sink is closed.
-  StreamController<T> _streamController;
+  late final StreamController<T> _streamController;
 
   /// The subscription to the inner stream.
-  StreamSubscription<T> _subscription;
+  StreamSubscription<T>? _subscription;
 
   /// Whether the sink has closed, causing the underlying channel to disconnect.
   bool _disconnected = false;
@@ -64,7 +64,8 @@
   /// should stop emitting events.
   void _onSinkDisconnected() {
     _disconnected = true;
-    if (_subscription != null) _subscription.cancel();
+    var subscription = _subscription;
+    if (subscription != null) subscription.cancel();
     _streamController.close();
   }
 }
@@ -95,11 +96,11 @@
 
   /// The subscription to the stream passed to [addStream], if a stream is
   /// currently being added.
-  StreamSubscription<T> _addStreamSubscription;
+  StreamSubscription<T>? _addStreamSubscription;
 
   /// The completer for the future returned by [addStream], if a stream is
   /// currently being added.
-  Completer _addStreamCompleter;
+  Completer? _addStreamCompleter;
 
   /// Whether we're currently adding a stream with [addStream].
   bool get _inAddStream => _addStreamSubscription != null;
@@ -125,7 +126,7 @@
   }
 
   @override
-  void addError(error, [StackTrace stackTrace]) {
+  void addError(Object error, [StackTrace? stackTrace]) {
     if (_closed) throw StateError('Cannot add event after closing.');
     if (_inAddStream) {
       throw StateError('Cannot add event while adding stream.');
@@ -139,7 +140,7 @@
   ///
   /// This is called from [addStream], so it shouldn't fail if a stream is being
   /// added.
-  void _addError(error, [StackTrace stackTrace]) {
+  void _addError(Object error, [StackTrace? stackTrace]) {
     if (_allowErrors) {
       _inner.addError(error, stackTrace);
       return;
@@ -166,8 +167,8 @@
 
     _addStreamCompleter = Completer.sync();
     _addStreamSubscription = stream.listen(_inner.add,
-        onError: _addError, onDone: _addStreamCompleter.complete);
-    return _addStreamCompleter.future.then((_) {
+        onError: _addError, onDone: _addStreamCompleter!.complete);
+    return _addStreamCompleter!.future.then((_) {
       _addStreamCompleter = null;
       _addStreamSubscription = null;
     });
@@ -199,7 +200,7 @@
     if (!_doneCompleter.isCompleted) _doneCompleter.complete();
 
     if (!_inAddStream) return;
-    _addStreamCompleter.complete(_addStreamSubscription.cancel());
+    _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
     _addStreamCompleter = null;
     _addStreamSubscription = null;
   }
diff --git a/lib/src/isolate_channel.dart b/lib/src/isolate_channel.dart
index 8c1b568..55c9814 100644
--- a/lib/src/isolate_channel.dart
+++ b/lib/src/isolate_channel.dart
@@ -51,7 +51,7 @@
     // The first message across the ReceivePort should be a SendPort pointing to
     // the remote end. If it's not, we'll make the stream emit an error
     // complaining.
-    StreamSubscription<dynamic> subscription;
+    late StreamSubscription<dynamic> subscription;
     subscription = receivePort.listen((message) {
       if (message is SendPort) {
         var controller =
diff --git a/lib/src/json_document_transformer.dart b/lib/src/json_document_transformer.dart
index 8bffc8a..3feda43 100644
--- a/lib/src/json_document_transformer.dart
+++ b/lib/src/json_document_transformer.dart
@@ -7,7 +7,6 @@
 import 'package:async/async.dart';
 
 import '../stream_channel.dart';
-import 'stream_channel_transformer.dart';
 
 /// A [StreamChannelTransformer] that transforms JSON documents—strings that
 /// contain individual objects encoded as JSON—into decoded Dart objects.
@@ -18,14 +17,14 @@
 /// If the transformed channel emits invalid JSON, this emits a
 /// [FormatException]. If an unencodable object is added to the sink, it
 /// synchronously throws a [JsonUnsupportedObjectError].
-final StreamChannelTransformer<Object, String> jsonDocument =
+final StreamChannelTransformer<Object?, String> jsonDocument =
     const _JsonDocument();
 
-class _JsonDocument implements StreamChannelTransformer<Object, String> {
+class _JsonDocument implements StreamChannelTransformer<Object?, String> {
   const _JsonDocument();
 
   @override
-  StreamChannel<Object> bind(StreamChannel<String> channel) {
+  StreamChannel<Object?> bind(StreamChannel<String> channel) {
     var stream = channel.stream.map(jsonDecode);
     var sink = StreamSinkTransformer<Object, String>.fromHandlers(
         handleData: (data, sink) {
diff --git a/lib/src/multi_channel.dart b/lib/src/multi_channel.dart
index 79f247e..82f59c7 100644
--- a/lib/src/multi_channel.dart
+++ b/lib/src/multi_channel.dart
@@ -73,7 +73,7 @@
   ///
   /// Throws an [ArgumentError] if a virtual channel already exists for [id].
   /// Throws a [StateError] if the underlying channel is closed.
-  VirtualChannel<T> virtualChannel([int id]);
+  VirtualChannel<T> virtualChannel([int? id]);
 }
 
 /// The implementation of [MultiChannel].
@@ -85,10 +85,10 @@
   /// The inner channel over which all communication is conducted.
   ///
   /// This will be `null` if the underlying communication channel is closed.
-  StreamChannel<dynamic> _inner;
+  StreamChannel<dynamic>? _inner;
 
   /// The subscription to [_inner].stream.
-  StreamSubscription<dynamic> _innerStreamSubscription;
+  StreamSubscription<dynamic>? _innerStreamSubscription;
 
   @override
   Stream<T> get stream => _mainController.foreign.stream;
@@ -132,16 +132,16 @@
   /// it's coming from a channel that was originally created locally.
   var _nextId = 1;
 
-  _MultiChannel(this._inner) {
+  _MultiChannel(StreamChannel<dynamic> inner) : _inner = inner {
     // The default connection is a special case which has id 0 on both ends.
     // This allows it to begin connected without having to send over an id.
     _controllers[0] = _mainController;
     _mainController.local.stream.listen(
-        (message) => _inner.sink.add([0, message]),
+        (message) => _inner!.sink.add(<Object?>[0, message]),
         onDone: () => _closeChannel(0, 0));
 
-    _innerStreamSubscription = _inner.stream.cast<List>().listen((message) {
-      var id = message[0] as int;
+    _innerStreamSubscription = _inner!.stream.cast<List>().listen((message) {
+      var id = (message[0] as num).toInt();
 
       // If the channel was closed before an incoming message was processed,
       // ignore that message.
@@ -170,7 +170,7 @@
   }
 
   @override
-  VirtualChannel<T> virtualChannel([int id]) {
+  VirtualChannel<T> virtualChannel([int? id]) {
     int inputId;
     int outputId;
     if (id != null) {
@@ -194,11 +194,11 @@
       return VirtualChannel._(this, inputId, Stream.empty(), NullStreamSink());
     }
 
-    StreamChannelController<T> controller;
+    late StreamChannelController<T> controller;
     if (_pendingIds.remove(inputId)) {
       // If we've already received messages for this channel, use the controller
       // where those messages are buffered.
-      controller = _controllers[inputId];
+      controller = _controllers[inputId]!;
     } else if (_controllers.containsKey(inputId) ||
         _closedIds.contains(inputId)) {
       throw ArgumentError('A virtual channel with id $id already exists.');
@@ -208,7 +208,7 @@
     }
 
     controller.local.stream.listen(
-        (message) => _inner.sink.add([outputId, message]),
+        (message) => _inner!.sink.add(<Object?>[outputId, message]),
         onDone: () => _closeChannel(inputId, outputId));
     return VirtualChannel._(
         this, outputId, controller.foreign.stream, controller.foreign.sink);
@@ -218,26 +218,26 @@
   /// outgoing messages have [outputId].
   void _closeChannel(int inputId, int outputId) {
     _closedIds.add(inputId);
-    var controller = _controllers.remove(inputId);
+    var controller = _controllers.remove(inputId)!;
     controller.local.sink.close();
 
     if (_inner == null) return;
 
     // A message without data indicates that the virtual channel has been
     // closed.
-    _inner.sink.add([outputId]);
+    _inner!.sink.add([outputId]);
     if (_controllers.isEmpty) _closeInnerChannel();
   }
 
   /// Closes the underlying communication channel.
   void _closeInnerChannel() {
-    _inner.sink.close();
-    _innerStreamSubscription.cancel();
+    _inner!.sink.close();
+    _innerStreamSubscription!.cancel();
     _inner = null;
 
     // Convert this to a list because the close is dispatched synchronously, and
     // that could conceivably remove a controller from [_controllers].
-    for (var controller in List.from(_controllers.values)) {
+    for (var controller in _controllers.values.toList(growable: false)) {
       controller.local.sink.close();
     }
     _controllers.clear();
@@ -269,5 +269,5 @@
   VirtualChannel._(this._parent, this.id, this.stream, this.sink);
 
   @override
-  VirtualChannel<T> virtualChannel([id]) => _parent.virtualChannel(id);
+  VirtualChannel<T> virtualChannel([int? id]) => _parent.virtualChannel(id);
 }
diff --git a/lib/src/stream_channel_completer.dart b/lib/src/stream_channel_completer.dart
index a14ffde..5a824c1 100644
--- a/lib/src/stream_channel_completer.dart
+++ b/lib/src/stream_channel_completer.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:async/async.dart';
 
 import '../stream_channel.dart';
@@ -22,7 +20,7 @@
 
   /// The channel for this completer.
   StreamChannel<T> get channel => _channel;
-  StreamChannel<T> _channel;
+  late final StreamChannel<T> _channel;
 
   /// Whether [setChannel] has been called.
   bool _set = false;
@@ -66,7 +64,7 @@
   ///
   /// Either [setChannel] or [setError] may be called at most once. Trying to
   /// call either of them again will fail.
-  void setError(error, [StackTrace stackTrace]) {
+  void setError(Object error, [StackTrace? stackTrace]) {
     if (_set) throw StateError('The channel has already been set.');
     _set = true;
 
diff --git a/lib/src/stream_channel_controller.dart b/lib/src/stream_channel_controller.dart
index 136886d..5c78866 100644
--- a/lib/src/stream_channel_controller.dart
+++ b/lib/src/stream_channel_controller.dart
@@ -33,14 +33,14 @@
   /// This channel should be used directly by the creator of this
   /// [StreamChannelController] to send and receive events.
   StreamChannel<T> get local => _local;
-  StreamChannel<T> _local;
+  late final StreamChannel<T> _local;
 
   /// The foreign channel.
   ///
   /// This channel should be returned to external users so they can communicate
   /// with [local].
   StreamChannel<T> get foreign => _foreign;
-  StreamChannel<T> _foreign;
+  late final StreamChannel<T> _foreign;
 
   /// Creates a [StreamChannelController].
   ///
diff --git a/lib/stream_channel.dart b/lib/stream_channel.dart
index 5693792..85f9a97 100644
--- a/lib/stream_channel.dart
+++ b/lib/stream_channel.dart
@@ -6,8 +6,8 @@
 
 import 'package:async/async.dart';
 
-import 'src/guarantee_channel.dart';
 import 'src/close_guarantee_channel.dart';
+import 'src/guarantee_channel.dart';
 import 'src/stream_channel_transformer.dart';
 
 export 'src/delegating_stream_channel.dart';
@@ -75,9 +75,9 @@
 
   /// Creates a new [StreamChannel] that communicates over [stream] and [sink].
   ///
-  /// Unlike [new StreamChannel], this enforces the guarantees listed in the
+  /// Unlike [StreamChannel.new], this enforces the guarantees listed in the
   /// [StreamChannel] documentation. This makes it somewhat less efficient than
-  /// just wrapping a stream and a sink directly, so [new StreamChannel] should
+  /// just wrapping a stream and a sink directly, so [StreamChannel.new] should
   /// be used when the guarantees are provided natively.
   ///
   /// If [allowSinkErrors] is `false`, errors are not allowed to be passed to
@@ -126,8 +126,9 @@
   /// Returns a copy of this with the generic type coerced to [S].
   ///
   /// If any events emitted by [stream] aren't of type [S], they're converted
-  /// into [CastError] events. Similarly, if any events are added to [sink] that
-  /// aren't of type [S], a [CastError] is thrown.
+  /// into [TypeError] events (`CastError` on some SDK versions). Similarly, if
+  /// any events are added to [sink] that aren't of type [S], a [TypeError] is
+  /// thrown.
   StreamChannel<S> cast<S>();
 }
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 61e78c0..5eb57ae 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,18 +1,16 @@
 name: stream_channel
-version: 2.1.0-dev
-
+version: 2.1.2-dev
 description: >-
   An abstraction for two-way communication channels based on the Dart Stream
   class.
-author: Dart Team <misc@dartlang.org>
-homepage: https://github.com/dart-lang/stream_channel
+repository: https://github.com/dart-lang/stream_channel
 
 environment:
-  sdk: '>=2.2.0 <3.0.0'
+  sdk: '>=2.19.0 <3.0.0'
 
 dependencies:
-  async: '>=1.11.0 <3.0.0'
+  async: ^2.5.0
 
 dev_dependencies:
-  pedantic: ^1.8.0
-  test: ^1.2.0
+  dart_flutter_team_lints: ^1.0.0
+  test: ^1.16.0
diff --git a/test/disconnector_test.dart b/test/disconnector_test.dart
index ec0c64b..28f3fee 100644
--- a/test/disconnector_test.dart
+++ b/test/disconnector_test.dart
@@ -5,15 +5,14 @@
 import 'dart:async';
 
 import 'package:async/async.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  StreamController streamController;
-  StreamController sinkController;
-  Disconnector disconnector;
-  StreamChannel channel;
+  late StreamController streamController;
+  late StreamController sinkController;
+  late Disconnector disconnector;
+  late StreamChannel channel;
   setUp(() {
     streamController = StreamController();
     sinkController = StreamController();
@@ -143,7 +142,7 @@
   /// The completer for the future returned by [close].
   final completer = Completer();
 
-  _CloseCompleterSink(StreamSink inner) : super(inner);
+  _CloseCompleterSink(super.inner);
 
   @override
   Future<void> close() {
diff --git a/test/isolate_channel_test.dart b/test/isolate_channel_test.dart
index ab70f74..1850664 100644
--- a/test/isolate_channel_test.dart
+++ b/test/isolate_channel_test.dart
@@ -3,19 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 
 @TestOn('vm')
+library;
 
 import 'dart:async';
 import 'dart:isolate';
 
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/isolate_channel.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  ReceivePort receivePort;
-  SendPort sendPort;
-  StreamChannel channel;
+  late ReceivePort receivePort;
+  late SendPort sendPort;
+  late StreamChannel channel;
   setUp(() {
     receivePort = ReceivePort();
     var receivePortForSend = ReceivePort();
@@ -128,7 +128,7 @@
   });
 
   group('connect constructors', () {
-    ReceivePort connectPort;
+    late ReceivePort connectPort;
     setUp(() {
       connectPort = ReceivePort();
     });
@@ -137,19 +137,21 @@
       connectPort.close();
     });
 
-    test('create a connected pair of channels', () {
+    test('create a connected pair of channels', () async {
       var channel1 = IsolateChannel<int>.connectReceive(connectPort);
       var channel2 = IsolateChannel<int>.connectSend(connectPort.sendPort);
 
       channel1.sink.add(1);
       channel1.sink.add(2);
       channel1.sink.add(3);
-      expect(channel2.stream.take(3).toList(), completion(equals([1, 2, 3])));
+      expect(await channel2.stream.take(3).toList(), equals([1, 2, 3]));
 
       channel2.sink.add(4);
       channel2.sink.add(5);
       channel2.sink.add(6);
-      expect(channel1.stream.take(3).toList(), completion(equals([4, 5, 6])));
+      expect(await channel1.stream.take(3).toList(), equals([4, 5, 6]));
+
+      await channel2.sink.close();
     });
 
     test('the receiving channel produces an error if it gets the wrong message',
diff --git a/test/json_document_transformer_test.dart b/test/json_document_transformer_test.dart
index 3ccce4e..48d8f72 100644
--- a/test/json_document_transformer_test.dart
+++ b/test/json_document_transformer_test.dart
@@ -9,9 +9,9 @@
 import 'package:test/test.dart';
 
 void main() {
-  StreamController<String> streamController;
-  StreamController<String> sinkController;
-  StreamChannel<String> channel;
+  late StreamController<String> streamController;
+  late StreamController<String> sinkController;
+  late StreamChannel<String> channel;
   setUp(() {
     streamController = StreamController<String>();
     sinkController = StreamController<String>();
diff --git a/test/multi_channel_test.dart b/test/multi_channel_test.dart
index 0fa1df9..ee6f8d2 100644
--- a/test/multi_channel_test.dart
+++ b/test/multi_channel_test.dart
@@ -2,14 +2,15 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'package:pedantic/pedantic.dart';
+import 'dart:async';
+
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  StreamChannelController controller;
-  MultiChannel channel1;
-  MultiChannel channel2;
+  late StreamChannelController controller;
+  late MultiChannel channel1;
+  late MultiChannel channel2;
   setUp(() {
     controller = StreamChannelController();
     channel1 = MultiChannel<int>(controller.local);
@@ -85,8 +86,8 @@
   });
 
   group('a locally-created virtual channel', () {
-    VirtualChannel virtual1;
-    VirtualChannel virtual2;
+    late VirtualChannel virtual1;
+    late VirtualChannel virtual2;
     setUp(() {
       virtual1 = channel1.virtualChannel();
       virtual2 = channel2.virtualChannel(virtual1.id);
@@ -185,8 +186,8 @@
   });
 
   group('a remotely-created virtual channel', () {
-    VirtualChannel virtual1;
-    VirtualChannel virtual2;
+    late VirtualChannel virtual1;
+    late VirtualChannel virtual2;
     setUp(() {
       virtual1 = channel1.virtualChannel();
       virtual2 = channel2.virtualChannel(virtual1.id);
@@ -297,8 +298,8 @@
   });
 
   group('when the underlying stream', () {
-    VirtualChannel virtual1;
-    VirtualChannel virtual2;
+    late VirtualChannel virtual1;
+    late VirtualChannel virtual2;
     setUp(() {
       virtual1 = channel1.virtualChannel();
       virtual2 = channel2.virtualChannel(virtual1.id);
@@ -347,8 +348,8 @@
   group('stream channel rules', () {
     group('for the main stream:', () {
       test(
-          'closing the sink causes the stream to close before it emits any more '
-          'events', () {
+          'closing the sink causes the stream to close before it emits any '
+          'more events', () {
         channel1.sink.add(1);
         channel1.sink.add(2);
         channel1.sink.add(3);
@@ -369,7 +370,8 @@
         channel2.sink.add(3);
         unawaited(channel2.sink.close());
 
-        // None of our channel.sink additions should make it to the other endpoint.
+        // None of our channel.sink additions should make it to the other
+        // endpoint.
         channel1.stream.listen(expectAsync1((_) {}, count: 0));
         await pumpEventQueue();
       });
@@ -406,16 +408,16 @@
     });
 
     group('for a virtual channel:', () {
-      VirtualChannel virtual1;
-      VirtualChannel virtual2;
+      late VirtualChannel virtual1;
+      late VirtualChannel virtual2;
       setUp(() {
         virtual1 = channel1.virtualChannel();
         virtual2 = channel2.virtualChannel(virtual1.id);
       });
 
       test(
-          'closing the sink causes the stream to close before it emits any more '
-          'events', () {
+          'closing the sink causes the stream to close before it emits any '
+          'more events', () {
         virtual1.sink.add(1);
         virtual1.sink.add(2);
         virtual1.sink.add(3);
@@ -436,7 +438,8 @@
         virtual2.sink.add(3);
         unawaited(virtual2.sink.close());
 
-        // None of our virtual.sink additions should make it to the other endpoint.
+        // None of our virtual.sink additions should make it to the other
+        // endpoint.
         virtual1.stream.listen(expectAsync1((_) {}, count: 0));
         await pumpEventQueue();
       });
diff --git a/test/stream_channel_completer_test.dart b/test/stream_channel_completer_test.dart
index 234f956..22db01a 100644
--- a/test/stream_channel_completer_test.dart
+++ b/test/stream_channel_completer_test.dart
@@ -4,15 +4,14 @@
 
 import 'dart:async';
 
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  StreamChannelCompleter completer;
-  StreamController streamController;
-  StreamController sinkController;
-  StreamChannel innerChannel;
+  late StreamChannelCompleter completer;
+  late StreamController streamController;
+  late StreamController sinkController;
+  late StreamChannel innerChannel;
   setUp(() {
     completer = StreamChannelCompleter();
     streamController = StreamController();
diff --git a/test/stream_channel_controller_test.dart b/test/stream_channel_controller_test.dart
index 9b7a851..3d661e3 100644
--- a/test/stream_channel_controller_test.dart
+++ b/test/stream_channel_controller_test.dart
@@ -7,7 +7,7 @@
 
 void main() {
   group('asynchronously', () {
-    StreamChannelController controller;
+    late StreamChannelController controller;
     setUp(() {
       controller = StreamChannelController();
     });
@@ -44,7 +44,7 @@
   });
 
   group('synchronously', () {
-    StreamChannelController controller;
+    late StreamChannelController controller;
     setUp(() {
       controller = StreamChannelController(sync: true);
     });
diff --git a/test/stream_channel_test.dart b/test/stream_channel_test.dart
index 9bd5a86..76edbdf 100644
--- a/test/stream_channel_test.dart
+++ b/test/stream_channel_test.dart
@@ -6,14 +6,13 @@
 import 'dart:convert';
 
 import 'package:async/async.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  StreamController streamController;
-  StreamController sinkController;
-  StreamChannel channel;
+  late StreamController streamController;
+  late StreamController sinkController;
+  late StreamChannel channel;
   setUp(() {
     streamController = StreamController();
     sinkController = StreamController();
diff --git a/test/with_close_guarantee_test.dart b/test/with_close_guarantee_test.dart
index 24aef03..a18f09f 100644
--- a/test/with_close_guarantee_test.dart
+++ b/test/with_close_guarantee_test.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 
 import 'package:async/async.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
@@ -17,8 +16,8 @@
     StreamSinkTransformer.fromStreamTransformer(_delayTransformer);
 
 void main() {
-  StreamChannelController controller;
-  StreamChannel channel;
+  late StreamChannelController controller;
+  late StreamChannel channel;
   setUp(() {
     controller = StreamChannelController();
 
diff --git a/test/with_guarantees_test.dart b/test/with_guarantees_test.dart
index fa49689..849e304 100644
--- a/test/with_guarantees_test.dart
+++ b/test/with_guarantees_test.dart
@@ -4,14 +4,13 @@
 
 import 'dart:async';
 
-import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
 void main() {
-  StreamController streamController;
-  StreamController sinkController;
-  StreamChannel channel;
+  late StreamController streamController;
+  late StreamController sinkController;
+  late StreamChannel channel;
   setUp(() {
     streamController = StreamController();
     sinkController = StreamController();
@@ -152,7 +151,7 @@
       channel.sink.addError('oh no');
       expect(channel.sink.done, throwsA('oh no'));
       sinkController.stream
-          .listen(null, onError: expectAsync1((_) {}, count: 0));
+          .listen(null, onError: expectAsync1((dynamic _) {}, count: 0));
     });
 
     test('adding an error causes the stream to emit a done event', () {