migrate to null safety (#37)

diff --git a/.travis.yml b/.travis.yml
index e1bb9d9..2acf002 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 language: dart
 
 dart:
-- 2.1.0
 - dev
 
 dart_task:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0ca552..648f9d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
+# 1.0.0-nullsafety.0
+
+* Update to null safety
+
 # 0.9.7
+
 * Add `extensionFromMime` utility function.
 
 # 0.9.6+3
diff --git a/lib/src/bound_multipart_stream.dart b/lib/src/bound_multipart_stream.dart
index 71c77f9..73bc495 100644
--- a/lib/src/bound_multipart_stream.dart
+++ b/lib/src/bound_multipart_stream.dart
@@ -62,8 +62,8 @@
   _MimeMultipart(this.headers, this._stream);
 
   @override
-  StreamSubscription<List<int>> listen(void Function(List<int> data) onData,
-      {void Function() onDone, Function onError, bool cancelOnError}) {
+  StreamSubscription<List<int>> listen(void Function(List<int> data)? onData,
+      {void Function()? onDone, Function? onError, bool? cancelOnError}) {
     return _stream.listen(onData,
         onDone: onDone, onError: onError, cancelOnError: cancelOnError);
   }
@@ -105,10 +105,10 @@
 
   Stream<MimeMultipart> get stream => _controller.stream;
 
-  StreamSubscription _subscription;
+  late StreamSubscription _subscription;
 
-  StreamController<List<int>> _multipartController;
-  Map<String, String> _headers;
+  StreamController<List<int>>? _multipartController;
+  Map<String, String>? _headers;
 
   int _state = _START;
   int _boundaryIndex = 2;
@@ -117,8 +117,8 @@
   ///
   /// If index is negative then it is the index into the artificial prefix of
   /// the boundary string.
-  int _index;
-  List<int> _buffer;
+  int _index = 0;
+  List<int> _buffer = _placeholderBuffer;
 
   BoundMultipartStream(this._boundary, Stream<List<int>> stream) {
     _controller
@@ -131,7 +131,7 @@
       ..onListen = () {
         _controllerState = _CONTROLLER_STATE_ACTIVE;
         _subscription = stream.listen((data) {
-          assert(_buffer == null);
+          assert(_buffer == _placeholderBuffer);
           _subscription.pause();
           _buffer = data;
           _index = 0;
@@ -193,18 +193,18 @@
     // prefix of the boundary both the content start index and index
     // can be negative.
     void reportData() {
-      if (contentStartIndex < 0) {
+      if (contentStartIndex! < 0) {
         var contentLength = boundaryPrefix + _index - _boundaryIndex;
         if (contentLength <= boundaryPrefix) {
-          _multipartController.add(_boundary.sublist(0, contentLength));
+          _multipartController!.add(_boundary.sublist(0, contentLength));
         } else {
-          _multipartController.add(_boundary.sublist(0, boundaryPrefix));
-          _multipartController
+          _multipartController!.add(_boundary.sublist(0, boundaryPrefix));
+          _multipartController!
               .add(_buffer.sublist(0, contentLength - boundaryPrefix));
         }
       } else {
         var contentEndIndex = _index - _boundaryIndex;
-        _multipartController
+        _multipartController!
             .add(_buffer.sublist(contentStartIndex, contentEndIndex));
       }
     }
@@ -239,8 +239,8 @@
 
         case _BOUNDARY_END:
           _expectByteValue(byte, CharCode.LF);
+          _multipartController?.close();
           if (_multipartController != null) {
-            _multipartController.close();
             _multipartController = null;
             _tryPropagateControllerState();
           }
@@ -298,7 +298,7 @@
           } else {
             var headerField = utf8.decode(_headerField);
             var headerValue = utf8.decode(_headerValue);
-            _headers[headerField.toLowerCase()] = headerValue;
+            _headers![headerField.toLowerCase()] = headerValue;
             _headerField.clear();
             _headerValue.clear();
             if (byte == CharCode.CR) {
@@ -321,7 +321,7 @@
               onPause: _subscription.pause,
               onResume: _subscription.resume);
           _controller
-              .add(_MimeMultipart(_headers, _multipartController.stream));
+              .add(_MimeMultipart(_headers!, _multipartController!.stream));
           _headers = null;
           _state = _CONTENT;
           contentStartIndex = _index + 1;
@@ -336,7 +336,7 @@
                 reportData();
                 _index--;
               }
-              _multipartController.close();
+              _multipartController!.close();
               _multipartController = null;
               _tryPropagateControllerState();
               _boundaryIndex = 0;
@@ -365,8 +365,8 @@
 
         case _LAST_BOUNDARY_END:
           _expectByteValue(byte, CharCode.LF);
+          _multipartController?.close();
           if (_multipartController != null) {
-            _multipartController.close();
             _multipartController = null;
             _tryPropagateControllerState();
           }
@@ -390,9 +390,12 @@
 
     // Resume if at end.
     if (_index == _buffer.length) {
-      _buffer = null;
-      _index = null;
+      _buffer = _placeholderBuffer;
+      _index = 0;
       _subscription.resume();
     }
   }
 }
+
+// Used as a placeholder instead of having a nullable buffer.
+const _placeholderBuffer = <int>[];
diff --git a/lib/src/magic_number.dart b/lib/src/magic_number.dart
index 50be18a..8933677 100644
--- a/lib/src/magic_number.dart
+++ b/lib/src/magic_number.dart
@@ -7,7 +7,7 @@
 class MagicNumber {
   final String mimeType;
   final List<int> numbers;
-  final List<int> mask;
+  final List<int>? mask;
 
   const MagicNumber(this.mimeType, this.numbers, {this.mask});
 
@@ -16,7 +16,7 @@
 
     for (var i = 0; i < numbers.length; i++) {
       if (mask != null) {
-        if ((mask[i] & numbers[i]) != (mask[i] & header[i])) return false;
+        if ((mask![i] & numbers[i]) != (mask![i] & header[i])) return false;
       } else {
         if (numbers[i] != header[i]) return false;
       }
diff --git a/lib/src/mime_type.dart b/lib/src/mime_type.dart
index 3b220e7..14db942 100644
--- a/lib/src/mime_type.dart
+++ b/lib/src/mime_type.dart
@@ -22,7 +22,7 @@
 /// a file have been saved using the wrong file-name extension. If less than
 /// [defaultMagicNumbersMaxLength] bytes was provided, some magic-numbers won't
 /// be matched against.
-String lookupMimeType(String path, {List<int> headerBytes}) =>
+String? lookupMimeType(String path, {List<int>? headerBytes}) =>
     _globalResolver.lookup(path, headerBytes: headerBytes);
 
 /// Returns the extension for the given MIME type.
@@ -69,8 +69,8 @@
   /// though a file have been saved using the wrong file-name extension. If less
   /// than [magicNumbersMaxLength] bytes was provided, some magic-numbers won't
   /// be matched against.
-  String lookup(String path, {List<int> headerBytes}) {
-    String result;
+  String? lookup(String path, {List<int>? headerBytes}) {
+    String? result;
     if (headerBytes != null) {
       result = _matchMagic(headerBytes, _magicNumbers);
       if (result != null) return result;
@@ -99,7 +99,7 @@
   ///
   /// If [mask] is present,the [mask] is used to only perform matching on
   /// selective bits. The [mask] must have the same length as [bytes].
-  void addMagicNumber(List<int> bytes, String mimeType, {List<int> mask}) {
+  void addMagicNumber(List<int> bytes, String mimeType, {List<int>? mask}) {
     if (mask != null && bytes.length != mask.length) {
       throw ArgumentError('Bytes and mask are of different lengths');
     }
@@ -109,7 +109,7 @@
     _magicNumbers.add(MagicNumber(mimeType, bytes, mask: mask));
   }
 
-  static String _matchMagic(
+  static String? _matchMagic(
       List<int> headerBytes, List<MagicNumber> magicNumbers) {
     for (var mn in magicNumbers) {
       if (mn.matches(headerBytes)) return mn.mimeType;
diff --git a/pubspec.yaml b/pubspec.yaml
index 17d31e1..6840fa9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: mime
-version: 0.9.8-dev
+version: 1.0.0-nullsafety.0
 
 description: >-
   Utilities for handling media (MIME) types, including determining a type from
@@ -7,8 +7,12 @@
 homepage: https://github.com/dart-lang/mime
 
 environment:
-  sdk: '>=2.1.0 <3.0.0'
+  sdk: '>=2.12.0-0 <3.0.0'
 
 dev_dependencies:
-  pedantic: ^1.0.0
-  test: ^1.2.0
+  pedantic: ^1.10.0-nullsafety
+  test: ^1.16.0-nullsafety
+
+dependency_overrides:
+  # Required due to package:test cycle.
+  shelf_static: ^0.2.8
diff --git a/test/mime_multipart_transformer_test.dart b/test/mime_multipart_transformer_test.dart
index fbfe2b6..6043d60 100644
--- a/test/mime_multipart_transformer_test.dart
+++ b/test/mime_multipart_transformer_test.dart
@@ -23,7 +23,9 @@
 enum TestMode { IMMEDIATE_LISTEN, DELAY_LISTEN, PAUSE_RESUME }
 
 void _runParseTest(String message, String boundary, TestMode mode,
-    [List<Map> expectedHeaders, List expectedParts, bool expectError = false]) {
+    [List<Map>? expectedHeaders,
+    List? expectedParts,
+    bool expectError = false]) {
   Future testWrite(List<int> data, [int chunkSize = -1]) {
     var controller = StreamController<List<int>>(sync: true);
 
@@ -39,20 +41,20 @@
       }
       switch (mode) {
         case TestMode.IMMEDIATE_LISTEN:
-          futures.add(multipart
-              .fold([], (buffer, data) => buffer..addAll(data)).then((data) {
-            if (expectedParts[part] != null) {
-              expect(data, equals(expectedParts[part].codeUnits));
+          futures.add(multipart.fold<List<int>>(
+              [], (buffer, data) => buffer..addAll(data)).then((data) {
+            if (expectedParts?[part] != null) {
+              expect(data, equals(expectedParts?[part].codeUnits));
             }
           }));
           break;
 
         case TestMode.DELAY_LISTEN:
           futures.add(Future(() {
-            return multipart
-                .fold([], (buffer, data) => buffer..addAll(data)).then((data) {
-              if (expectedParts[part] != null) {
-                expect(data, equals(expectedParts[part].codeUnits));
+            return multipart.fold<List<int>>(
+                [], (buffer, data) => buffer..addAll(data)).then((data) {
+              if (expectedParts?[part] != null) {
+                expect(data, equals(expectedParts?[part].codeUnits));
               }
             });
           }));
@@ -68,14 +70,14 @@
             subscription.pause();
             Future(() => subscription.resume());
           }, onDone: () {
-            if (expectedParts[part] != null) {
-              expect(buffer, equals(expectedParts[part].codeUnits));
+            if (expectedParts?[part] != null) {
+              expect(buffer, equals(expectedParts?[part].codeUnits));
             }
             completer.complete();
           });
           break;
       }
-    }, onError: (error) {
+    }, onError: (Object error) {
       if (!expectError) throw error;
     }, onDone: () {
       if (expectedParts != null) {
@@ -100,7 +102,8 @@
       if (expectedHeaders != null) {
         expect(multipart.headers, equals(expectedHeaders[0]));
       }
-      return (multipart.fold([], (b, d) => b..addAll(d)).then((data) {
+      return (multipart
+          .fold<List<int>>([], (b, d) => b..addAll(d)).then((data) {
         if (expectedParts != null && expectedParts[0] != null) {
           expect(data, equals(expectedParts[0].codeUnits));
         }
@@ -133,7 +136,8 @@
       if (expectedHeaders != null) {
         expect(multipart.headers, equals(expectedHeaders[partIndex]));
       }
-      futures.add((multipart.fold([], (b, d) => b..addAll(d)).then((data) {
+      futures.add(
+          (multipart.fold<List<int>>([], (b, d) => b..addAll(d)).then((data) {
         if (expectedParts != null && expectedParts[partIndex] != null) {
           expect(data, equals(expectedParts[partIndex].codeUnits));
         }
@@ -165,7 +169,7 @@
         completes);
   });
 
-  if (expectedParts.isNotEmpty) {
+  if (expectedParts!.isNotEmpty) {
     test('test-first-part-only', () {
       expect(
           Future.wait([
@@ -194,7 +198,9 @@
 }
 
 void _testParse(String message, String boundary,
-    [List<Map> expectedHeaders, List expectedParts, bool expectError = false]) {
+    [List<Map>? expectedHeaders,
+    List? expectedParts,
+    bool expectError = false]) {
   _runParseTest(message, boundary, TestMode.IMMEDIATE_LISTEN, expectedHeaders,
       expectedParts, expectError);
   _runParseTest(message, boundary, TestMode.DELAY_LISTEN, expectedHeaders,
diff --git a/test/mime_type_test.dart b/test/mime_type_test.dart
index 7f9169c..992b73d 100644
--- a/test/mime_type_test.dart
+++ b/test/mime_type_test.dart
@@ -8,9 +8,9 @@
 import 'package:mime/mime.dart';
 import 'package:mime/src/magic_number.dart';
 
-void _expectMimeType(String path, String expectedMimeType,
-    {List<int> headerBytes, MimeTypeResolver resolver}) {
-  String mimeType;
+void _expectMimeType(String path, String? expectedMimeType,
+    {List<int>? headerBytes, MimeTypeResolver? resolver}) {
+  String? mimeType;
   if (resolver == null) {
     mimeType = lookupMimeType(path, headerBytes: headerBytes);
   } else {