Reland "[cross_file] Migrate to null-safety. (#3452)" (#3469)

This reverts commit 4aacf970f3891c9cf23c768ca1c8bec04c42b35a.
diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md
index 45f516a..c9b3d1a 100644
--- a/packages/cross_file/CHANGELOG.md
+++ b/packages/cross_file/CHANGELOG.md
@@ -1,6 +1,12 @@
+## 0.3.0-nullsafety
+
+* Migrated package to null-safety.
+* **breaking change** According to our unit tests, the API should be backwards-compatible. Some relevant changes were made, however:
+  * Web: `lastModified` returns the epoch time as a default value, to maintain the `Future<DateTime>` return type (and not `null`)
+
 ## 0.2.1
 
-* Prepare for breaking `package:http` change. 
+* Prepare for breaking `package:http` change.
 
 ## 0.2.0
 
@@ -12,8 +18,8 @@
 
 ## 0.1.0+1
 
-- Update Flutter SDK constraint.
+* Update Flutter SDK constraint.
 
 ## 0.1.0
 
-- Initial open-source release
+* Initial open-source release.
diff --git a/packages/cross_file/lib/src/types/base.dart b/packages/cross_file/lib/src/types/base.dart
index 1a1b569..2a59c1c 100644
--- a/packages/cross_file/lib/src/types/base.dart
+++ b/packages/cross_file/lib/src/types/base.dart
@@ -15,7 +15,7 @@
 /// the methods should seem familiar.
 abstract class XFileBase {
   /// Construct a CrossFile
-  XFileBase(String path);
+  XFileBase(String? path);
 
   /// Save the CrossFile at the indicated file path.
   Future<void> saveTo(String path) {
@@ -31,19 +31,19 @@
   /// Accessing the data contained in the picked file by its path
   /// is platform-dependant (and won't work on web), so use the
   /// byte getters in the CrossFile instance instead.
-  String get path {
+  String? get path {
     throw UnimplementedError('.path has not been implemented.');
   }
 
   /// The name of the file as it was selected by the user in their device.
   ///
   /// Use only for cosmetic reasons, do not try to use this as a path.
-  String get name {
+  String? get name {
     throw UnimplementedError('.name has not been implemented.');
   }
 
   /// For web, it may be necessary for a file to know its MIME type.
-  String get mimeType {
+  String? get mimeType {
     throw UnimplementedError('.mimeType has not been implemented.');
   }
 
@@ -75,7 +75,7 @@
   /// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file.
   ///
   /// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled.
-  Stream<Uint8List> openRead([int start, int end]) {
+  Stream<Uint8List> openRead([int? start, int? end]) {
     throw UnimplementedError('openRead() has not been implemented.');
   }
 
diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart
index 527d5e6..203ab5d 100644
--- a/packages/cross_file/lib/src/types/html.dart
+++ b/packages/cross_file/lib/src/types/html.dart
@@ -6,7 +6,6 @@
 import 'dart:html';
 import 'dart:typed_data';
 
-import 'package:http/http.dart' as http show readBytes;
 import 'package:meta/meta.dart';
 
 import './base.dart';
@@ -16,16 +15,17 @@
 ///
 /// It wraps the bytes of a selected file.
 class XFile extends XFileBase {
-  String path;
+  late String path;
 
-  final String mimeType;
-  final Uint8List _data;
-  final int _length;
+  final String? mimeType;
+  final Uint8List? _data;
+  final int? _length;
   final String name;
-  final DateTime _lastModified;
-  Element _target;
+  final DateTime? _lastModified;
 
-  final CrossFileTestOverrides _overrides;
+  late Element _target;
+
+  final CrossFileTestOverrides? _overrides;
 
   bool get _hasTestOverrides => _overrides != null;
 
@@ -39,56 +39,58 @@
   XFile(
     this.path, {
     this.mimeType,
-    this.name,
-    int length,
-    Uint8List bytes,
-    DateTime lastModified,
-    @visibleForTesting CrossFileTestOverrides overrides,
+    String? name,
+    int? length,
+    Uint8List? bytes,
+    DateTime? lastModified,
+    @visibleForTesting CrossFileTestOverrides? overrides,
   })  : _data = bytes,
         _length = length,
         _overrides = overrides,
-        _lastModified = lastModified,
+        _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0),
+        name = name ?? '',
         super(path);
 
   /// Construct an CrossFile from its data
   XFile.fromData(
     Uint8List bytes, {
     this.mimeType,
-    this.name,
-    int length,
-    DateTime lastModified,
-    this.path,
-    @visibleForTesting CrossFileTestOverrides overrides,
+    String? name,
+    int? length,
+    DateTime? lastModified,
+    String? path,
+    @visibleForTesting CrossFileTestOverrides? overrides,
   })  : _data = bytes,
         _length = length,
         _overrides = overrides,
-        _lastModified = lastModified,
+        _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0),
+        name = name ?? '',
         super(path) {
     if (path == null) {
       final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType);
       this.path = Url.createObjectUrl(blob);
+    } else {
+      this.path = path;
     }
   }
 
   @override
-  Future<DateTime> lastModified() async {
-    if (_lastModified != null) {
-      return Future.value(_lastModified);
-    }
-    return null;
-  }
+  Future<DateTime> lastModified() async => Future.value(_lastModified);
 
   Future<Uint8List> get _bytes async {
     if (_data != null) {
-      return Future.value(UnmodifiableUint8ListView(_data));
+      return Future.value(UnmodifiableUint8ListView(_data!));
     }
-    return http.readBytes(Uri.parse(path));
+
+    // We can force 'response' to be a byte buffer by passing responseType:
+    ByteBuffer? response =
+        (await HttpRequest.request(path, responseType: 'arraybuffer')).response;
+
+    return response?.asUint8List() ?? Uint8List(0);
   }
 
   @override
-  Future<int> length() async {
-    return _length ?? (await _bytes).length;
-  }
+  Future<int> length() async => _length ?? (await _bytes).length;
 
   @override
   Future<String> readAsString({Encoding encoding = utf8}) async {
@@ -96,12 +98,10 @@
   }
 
   @override
-  Future<Uint8List> readAsBytes() async {
-    return Future.value(await _bytes);
-  }
+  Future<Uint8List> readAsBytes() async => Future.value(await _bytes);
 
   @override
-  Stream<Uint8List> openRead([int start, int end]) async* {
+  Stream<Uint8List> openRead([int? start, int? end]) async* {
     final bytes = await _bytes;
     yield bytes.sublist(start ?? 0, end ?? bytes.length);
   }
@@ -114,10 +114,9 @@
 
     // Create an <a> tag with the appropriate download attributes and click it
     // May be overridden with CrossFileTestOverrides
-    final AnchorElement element =
-        (_hasTestOverrides && _overrides.createAnchorElement != null)
-            ? _overrides.createAnchorElement(this.path, this.name)
-            : createAnchorElement(this.path, this.name);
+    final AnchorElement element = _hasTestOverrides
+        ? _overrides!.createAnchorElement(this.path, this.name) as AnchorElement
+        : createAnchorElement(this.path, this.name);
 
     // Clear the children in our container so we can add an element to click
     _target.children.clear();
@@ -132,5 +131,5 @@
   Element Function(String href, String suggestedName) createAnchorElement;
 
   /// Default constructor for overrides
-  CrossFileTestOverrides({this.createAnchorElement});
+  CrossFileTestOverrides({required this.createAnchorElement});
 }
diff --git a/packages/cross_file/lib/src/types/interface.dart b/packages/cross_file/lib/src/types/interface.dart
index e30bc63..122f3d1 100644
--- a/packages/cross_file/lib/src/types/interface.dart
+++ b/packages/cross_file/lib/src/types/interface.dart
@@ -21,12 +21,12 @@
   /// (like in web)
   XFile(
     String path, {
-    String mimeType,
-    String name,
-    int length,
-    Uint8List bytes,
-    DateTime lastModified,
-    @visibleForTesting CrossFileTestOverrides overrides,
+    String? mimeType,
+    String? name,
+    int? length,
+    Uint8List? bytes,
+    DateTime? lastModified,
+    @visibleForTesting CrossFileTestOverrides? overrides,
   }) : super(path) {
     throw UnimplementedError(
         'CrossFile is not available in your current platform.');
@@ -35,12 +35,12 @@
   /// Construct a CrossFile object from its data
   XFile.fromData(
     Uint8List bytes, {
-    String mimeType,
-    String name,
-    int length,
-    DateTime lastModified,
-    String path,
-    @visibleForTesting CrossFileTestOverrides overrides,
+    String? mimeType,
+    String? name,
+    int? length,
+    DateTime? lastModified,
+    String? path,
+    @visibleForTesting CrossFileTestOverrides? overrides,
   }) : super(path) {
     throw UnimplementedError(
         'CrossFile is not available in your current platform.');
@@ -54,5 +54,5 @@
   dynamic Function(String href, String suggestedName) createAnchorElement;
 
   /// Default constructor for overrides
-  CrossFileTestOverrides({this.createAnchorElement});
+  CrossFileTestOverrides({required this.createAnchorElement});
 }
diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart
index d9a9355..6eafaf0 100644
--- a/packages/cross_file/lib/src/types/io.dart
+++ b/packages/cross_file/lib/src/types/io.dart
@@ -11,20 +11,20 @@
 /// A CrossFile backed by a dart:io File.
 class XFile extends XFileBase {
   final File _file;
-  final String mimeType;
-  final DateTime _lastModified;
-  int _length;
+  final String? mimeType;
+  final DateTime? _lastModified;
+  int? _length;
 
-  final Uint8List _bytes;
+  final Uint8List? _bytes;
 
   /// Construct a CrossFile object backed by a dart:io File.
   XFile(
     String path, {
     this.mimeType,
-    String name,
-    int length,
-    Uint8List bytes,
-    DateTime lastModified,
+    String? name,
+    int? length,
+    Uint8List? bytes,
+    DateTime? lastModified,
   })  : _file = File(path),
         _bytes = null,
         _lastModified = lastModified,
@@ -34,10 +34,10 @@
   XFile.fromData(
     Uint8List bytes, {
     this.mimeType,
-    String path,
-    String name,
-    int length,
-    DateTime lastModified,
+    String? path,
+    String? name,
+    int? length,
+    DateTime? lastModified,
   })  : _bytes = bytes,
         _file = File(path ?? ''),
         _length = length,
@@ -84,7 +84,7 @@
   @override
   Future<String> readAsString({Encoding encoding = utf8}) {
     if (_bytes != null) {
-      return Future.value(String.fromCharCodes(_bytes));
+      return Future.value(String.fromCharCodes(_bytes!));
     }
     return _file.readAsString(encoding: encoding);
   }
@@ -97,13 +97,13 @@
     return _file.readAsBytes();
   }
 
-  Stream<Uint8List> _getBytes(int start, int end) async* {
-    final bytes = _bytes;
+  Stream<Uint8List> _getBytes(int? start, int? end) async* {
+    final bytes = _bytes!;
     yield bytes.sublist(start ?? 0, end ?? bytes.length);
   }
 
   @override
-  Stream<Uint8List> openRead([int start, int end]) {
+  Stream<Uint8List> openRead([int? start, int? end]) {
     if (_bytes != null) {
       return _getBytes(start, end);
     } else {
diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart
index 813f5f9..a963e99 100644
--- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart
+++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart
@@ -31,7 +31,7 @@
   if (target == null) {
     final Element targetElement = Element.tag('flt-x-file')..id = id;
 
-    querySelector('body').children.add(targetElement);
+    querySelector('body')!.children.add(targetElement);
     target = targetElement;
   }
   return target;
diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml
index 2228674..af1b7e7 100644
--- a/packages/cross_file/pubspec.yaml
+++ b/packages/cross_file/pubspec.yaml
@@ -1,19 +1,18 @@
 name: cross_file
 description: An abstraction to allow working with files across multiple platforms.
 homepage: https://github.com/flutter/plugins/tree/master/packages/cross_file
-version: 0.2.1
+version: 0.3.0-nullsafety
 
 dependencies:
   flutter:
     sdk: flutter
-  http: ^0.12.0+1
-  meta: ^1.0.5
+  meta: ^1.3.0-nullsafety.3
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
-  pedantic: ^1.8.0
+  pedantic: ^1.10.0-nullsafety.3
 
 environment:
-  sdk: ">=2.1.0 <3.0.0"
+  sdk: ">=2.12.0-0 <3.0.0"
   flutter: ">=1.22.0"
diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart
index fadba96..a271aa1 100644
--- a/packages/cross_file/test/x_file_html_test.dart
+++ b/packages/cross_file/test/x_file_html_test.dart
@@ -11,10 +11,8 @@
 import 'package:flutter_test/flutter_test.dart';
 import 'package:cross_file/cross_file.dart';
 
-import 'dart:html';
-
 final String expectedStringContents = 'Hello, world!';
-final Uint8List bytes = utf8.encode(expectedStringContents);
+final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents));
 final html.File textFile = html.File([bytes], 'hello.txt');
 final String textFileUrl = html.Url.createObjectUrl(textFile);
 
@@ -66,7 +64,7 @@
 
         await file.saveTo('');
 
-        final container = querySelector('#${CrossFileDomElementId}');
+        final container = html.querySelector('#${CrossFileDomElementId}');
 
         expect(container, isNotNull);
       });
@@ -76,18 +74,18 @@
 
         await file.saveTo('path');
 
-        final container = querySelector('#${CrossFileDomElementId}');
-        final AnchorElement element = container?.children?.firstWhere(
-            (element) => element.tagName == 'A',
-            orElse: () => null);
+        final container = html.querySelector('#${CrossFileDomElementId}');
+        final html.AnchorElement element =
+            container?.children.firstWhere((element) => element.tagName == 'A')
+                as html.AnchorElement;
 
-        expect(element, isNotNull);
+        // if element is not found, the `firstWhere` call will throw StateError.
         expect(element.href, file.path);
         expect(element.download, file.name);
       });
 
       test('anchor element is clicked', () async {
-        final mockAnchor = AnchorElement();
+        final mockAnchor = html.AnchorElement();
 
         CrossFileTestOverrides overrides = CrossFileTestOverrides(
           createAnchorElement: (_, __) => mockAnchor,
diff --git a/packages/cross_file/test/x_file_io_test.dart b/packages/cross_file/test/x_file_io_test.dart
index d45ff59..94ac81c 100644
--- a/packages/cross_file/test/x_file_io_test.dart
+++ b/packages/cross_file/test/x_file_io_test.dart
@@ -15,7 +15,7 @@
     Directory.current.path.endsWith('test') ? './assets/' : './test/assets/';
 final path = pathPrefix + 'hello.txt';
 final String expectedStringContents = 'Hello, world!';
-final Uint8List bytes = utf8.encode(expectedStringContents);
+final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents));
 final File textFile = File(path);
 final String textFilePath = textFile.path;
 
diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh
index 72390c2..3e08b91 100755
--- a/script/build_all_plugins_app.sh
+++ b/script/build_all_plugins_app.sh
@@ -23,14 +23,15 @@
   "connectivity_platform_interface"
   "connectivity_web"
   "extension_google_sign_in_as_googleapis_auth"
+  "file_selector" # currently out of sync with camera
   "flutter_plugin_android_lifecycle"
   "google_maps_flutter_platform_interface"
   "google_maps_flutter_web"
   "google_sign_in_platform_interface"
   "google_sign_in_web"
   "image_picker_platform_interface"
-  "local_auth" # flutter_plugin_android_lifecycle conflict
   "instrumentation_adapter"
+  "local_auth" # flutter_plugin_android_lifecycle conflict
   "path_provider_linux"
   "path_provider_macos"
   "path_provider_platform_interface"
diff --git a/script/nnbd_plugins.sh b/script/nnbd_plugins.sh
index b2ca25b..44e5df9 100644
--- a/script/nnbd_plugins.sh
+++ b/script/nnbd_plugins.sh
@@ -8,6 +8,7 @@
   "android_intent"
   "battery"
   "connectivity"
+  "cross_file"
   "device_info"
   "flutter_plugin_android_lifecycle"
   "flutter_webview"