From ca9921196a7ae96edad91a9cd7b7d8fe9f5689ff Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Jan 2021 11:16:40 -0800 Subject: [PATCH] [cross_file] Migrate to null-safety. (#3452) Also: skip some packages from the all_plugins app so CI passes. --- packages/cross_file/CHANGELOG.md | 12 ++- packages/cross_file/lib/src/types/base.dart | 10 +-- packages/cross_file/lib/src/types/html.dart | 79 +++++++++---------- .../cross_file/lib/src/types/interface.dart | 26 +++--- packages/cross_file/lib/src/types/io.dart | 32 ++++---- .../lib/src/web_helpers/web_helpers.dart | 2 +- packages/cross_file/pubspec.yaml | 9 +-- .../cross_file/test/x_file_html_test.dart | 18 ++--- packages/cross_file/test/x_file_io_test.dart | 2 +- script/build_all_plugins_app.sh | 3 +- script/nnbd_plugins.sh | 1 + 11 files changed, 99 insertions(+), 95 deletions(-) diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 45f516ad334d..c9b3d1ab2522 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` 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 1a1b5694d58f..2a59c1c2b246 100644 --- a/packages/cross_file/lib/src/types/base.dart +++ b/packages/cross_file/lib/src/types/base.dart @@ -15,7 +15,7 @@ import 'dart:typed_data'; /// 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 saveTo(String path) { @@ -31,19 +31,19 @@ abstract class XFileBase { /// 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 @@ abstract class XFileBase { /// 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 openRead([int start, int end]) { + Stream 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 527d5e6911f6..203ab5d82e12 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:convert'; 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 @@ import '../web_helpers/web_helpers.dart'; /// /// 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 @@ class XFile extends XFileBase { 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 lastModified() async { - if (_lastModified != null) { - return Future.value(_lastModified); - } - return null; - } + Future lastModified() async => Future.value(_lastModified); Future 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 length() async { - return _length ?? (await _bytes).length; - } + Future length() async => _length ?? (await _bytes).length; @override Future readAsString({Encoding encoding = utf8}) async { @@ -96,12 +98,10 @@ class XFile extends XFileBase { } @override - Future readAsBytes() async { - return Future.value(await _bytes); - } + Future readAsBytes() async => Future.value(await _bytes); @override - Stream openRead([int start, int end]) async* { + Stream openRead([int? start, int? end]) async* { final bytes = await _bytes; yield bytes.sublist(start ?? 0, end ?? bytes.length); } @@ -114,10 +114,9 @@ class XFile extends XFileBase { // Create an 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 @@ class CrossFileTestOverrides { 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 e30bc63b4c92..122f3d1d9364 100644 --- a/packages/cross_file/lib/src/types/interface.dart +++ b/packages/cross_file/lib/src/types/interface.dart @@ -21,12 +21,12 @@ class XFile extends XFileBase { /// (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 @@ class XFile extends XFileBase { /// 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 @@ class CrossFileTestOverrides { 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 d9a93559b507..6eafaf0ce0cc 100644 --- a/packages/cross_file/lib/src/types/io.dart +++ b/packages/cross_file/lib/src/types/io.dart @@ -11,20 +11,20 @@ import './base.dart'; /// 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 @@ class XFile extends XFileBase { 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 @@ class XFile extends XFileBase { @override Future 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 @@ class XFile extends XFileBase { return _file.readAsBytes(); } - Stream _getBytes(int start, int end) async* { - final bytes = _bytes; + Stream _getBytes(int? start, int? end) async* { + final bytes = _bytes!; yield bytes.sublist(start ?? 0, end ?? bytes.length); } @override - Stream openRead([int start, int end]) { + Stream 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 813f5f975561..a963e9933f99 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 @@ Element ensureInitialized(String id) { 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 2228674baf40..af1b7e7d4c0f 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 fadba96b3c6c..a271aa1f1525 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 'dart:typed_data'; 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 @@ void main() { await file.saveTo(''); - final container = querySelector('#${CrossFileDomElementId}'); + final container = html.querySelector('#${CrossFileDomElementId}'); expect(container, isNotNull); }); @@ -76,18 +74,18 @@ void main() { 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 65edea1ea45d..25f46a4edad9 100644 --- a/packages/cross_file/test/x_file_io_test.dart +++ b/packages/cross_file/test/x_file_io_test.dart @@ -24,7 +24,7 @@ import 'package:cross_file/cross_file.dart'; final pathPrefix = './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 72390c213da9..3e08b914ff86 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -23,14 +23,15 @@ readonly EXCLUDED_PLUGINS_LIST=( "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 b2ca25bf6836..44e5df9e95ef 100644 --- a/script/nnbd_plugins.sh +++ b/script/nnbd_plugins.sh @@ -8,6 +8,7 @@ readonly NNBD_PLUGINS_LIST=( "android_intent" "battery" "connectivity" + "cross_file" "device_info" "flutter_plugin_android_lifecycle" "flutter_webview"