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

Also: skip some packages from the all_plugins app so CI passes.
This commit is contained in:
David Iglesias
2021-01-26 11:16:40 -08:00
committed by GitHub
parent 52579675a0
commit 519abf9b5b
9 changed files with 96 additions and 94 deletions

View File

@ -1,3 +1,9 @@
## 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 ## 0.2.1
* Prepare for breaking `package:http` change. * Prepare for breaking `package:http` change.
@ -12,8 +18,8 @@
## 0.1.0+1 ## 0.1.0+1
- Update Flutter SDK constraint. * Update Flutter SDK constraint.
## 0.1.0 ## 0.1.0
- Initial open-source release * Initial open-source release.

View File

@ -15,7 +15,7 @@ import 'dart:typed_data';
/// the methods should seem familiar. /// the methods should seem familiar.
abstract class XFileBase { abstract class XFileBase {
/// Construct a CrossFile /// Construct a CrossFile
XFileBase(String path); XFileBase(String? path);
/// Save the CrossFile at the indicated file path. /// Save the CrossFile at the indicated file path.
Future<void> saveTo(String path) { Future<void> saveTo(String path) {
@ -31,19 +31,19 @@ abstract class XFileBase {
/// Accessing the data contained in the picked file by its path /// Accessing the data contained in the picked file by its path
/// is platform-dependant (and won't work on web), so use the /// is platform-dependant (and won't work on web), so use the
/// byte getters in the CrossFile instance instead. /// byte getters in the CrossFile instance instead.
String get path { String? get path {
throw UnimplementedError('.path has not been implemented.'); throw UnimplementedError('.path has not been implemented.');
} }
/// The name of the file as it was selected by the user in their device. /// 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. /// 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.'); throw UnimplementedError('.name has not been implemented.');
} }
/// For web, it may be necessary for a file to know its MIME type. /// 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.'); 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. /// 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. /// 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.'); throw UnimplementedError('openRead() has not been implemented.');
} }

View File

@ -6,7 +6,6 @@ import 'dart:convert';
import 'dart:html'; import 'dart:html';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart' as http show readBytes;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import './base.dart'; import './base.dart';
@ -16,16 +15,17 @@ import '../web_helpers/web_helpers.dart';
/// ///
/// It wraps the bytes of a selected file. /// It wraps the bytes of a selected file.
class XFile extends XFileBase { class XFile extends XFileBase {
String path; late String path;
final String mimeType; final String? mimeType;
final Uint8List _data; final Uint8List? _data;
final int _length; final int? _length;
final String name; final String name;
final DateTime _lastModified; final DateTime? _lastModified;
Element _target;
final CrossFileTestOverrides _overrides; late Element _target;
final CrossFileTestOverrides? _overrides;
bool get _hasTestOverrides => _overrides != null; bool get _hasTestOverrides => _overrides != null;
@ -39,56 +39,58 @@ class XFile extends XFileBase {
XFile( XFile(
this.path, { this.path, {
this.mimeType, this.mimeType,
this.name, String? name,
int length, int? length,
Uint8List bytes, Uint8List? bytes,
DateTime lastModified, DateTime? lastModified,
@visibleForTesting CrossFileTestOverrides overrides, @visibleForTesting CrossFileTestOverrides? overrides,
}) : _data = bytes, }) : _data = bytes,
_length = length, _length = length,
_overrides = overrides, _overrides = overrides,
_lastModified = lastModified, _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0),
name = name ?? '',
super(path); super(path);
/// Construct an CrossFile from its data /// Construct an CrossFile from its data
XFile.fromData( XFile.fromData(
Uint8List bytes, { Uint8List bytes, {
this.mimeType, this.mimeType,
this.name, String? name,
int length, int? length,
DateTime lastModified, DateTime? lastModified,
this.path, String? path,
@visibleForTesting CrossFileTestOverrides overrides, @visibleForTesting CrossFileTestOverrides? overrides,
}) : _data = bytes, }) : _data = bytes,
_length = length, _length = length,
_overrides = overrides, _overrides = overrides,
_lastModified = lastModified, _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0),
name = name ?? '',
super(path) { super(path) {
if (path == null) { if (path == null) {
final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType); final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType);
this.path = Url.createObjectUrl(blob); this.path = Url.createObjectUrl(blob);
} else {
this.path = path;
} }
} }
@override @override
Future<DateTime> lastModified() async { Future<DateTime> lastModified() async => Future.value(_lastModified);
if (_lastModified != null) {
return Future.value(_lastModified);
}
return null;
}
Future<Uint8List> get _bytes async { Future<Uint8List> get _bytes async {
if (_data != null) { 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 @override
Future<int> length() async { Future<int> length() async => _length ?? (await _bytes).length;
return _length ?? (await _bytes).length;
}
@override @override
Future<String> readAsString({Encoding encoding = utf8}) async { Future<String> readAsString({Encoding encoding = utf8}) async {
@ -96,12 +98,10 @@ class XFile extends XFileBase {
} }
@override @override
Future<Uint8List> readAsBytes() async { Future<Uint8List> readAsBytes() async => Future.value(await _bytes);
return Future.value(await _bytes);
}
@override @override
Stream<Uint8List> openRead([int start, int end]) async* { Stream<Uint8List> openRead([int? start, int? end]) async* {
final bytes = await _bytes; final bytes = await _bytes;
yield bytes.sublist(start ?? 0, end ?? bytes.length); yield bytes.sublist(start ?? 0, end ?? bytes.length);
} }
@ -114,9 +114,8 @@ class XFile extends XFileBase {
// Create an <a> tag with the appropriate download attributes and click it // Create an <a> tag with the appropriate download attributes and click it
// May be overridden with CrossFileTestOverrides // May be overridden with CrossFileTestOverrides
final AnchorElement element = final AnchorElement element = _hasTestOverrides
(_hasTestOverrides && _overrides.createAnchorElement != null) ? _overrides!.createAnchorElement(this.path, this.name) as AnchorElement
? _overrides.createAnchorElement(this.path, this.name)
: createAnchorElement(this.path, this.name); : createAnchorElement(this.path, this.name);
// Clear the children in our container so we can add an element to click // Clear the children in our container so we can add an element to click
@ -132,5 +131,5 @@ class CrossFileTestOverrides {
Element Function(String href, String suggestedName) createAnchorElement; Element Function(String href, String suggestedName) createAnchorElement;
/// Default constructor for overrides /// Default constructor for overrides
CrossFileTestOverrides({this.createAnchorElement}); CrossFileTestOverrides({required this.createAnchorElement});
} }

View File

@ -21,12 +21,12 @@ class XFile extends XFileBase {
/// (like in web) /// (like in web)
XFile( XFile(
String path, { String path, {
String mimeType, String? mimeType,
String name, String? name,
int length, int? length,
Uint8List bytes, Uint8List? bytes,
DateTime lastModified, DateTime? lastModified,
@visibleForTesting CrossFileTestOverrides overrides, @visibleForTesting CrossFileTestOverrides? overrides,
}) : super(path) { }) : super(path) {
throw UnimplementedError( throw UnimplementedError(
'CrossFile is not available in your current platform.'); 'CrossFile is not available in your current platform.');
@ -35,12 +35,12 @@ class XFile extends XFileBase {
/// Construct a CrossFile object from its data /// Construct a CrossFile object from its data
XFile.fromData( XFile.fromData(
Uint8List bytes, { Uint8List bytes, {
String mimeType, String? mimeType,
String name, String? name,
int length, int? length,
DateTime lastModified, DateTime? lastModified,
String path, String? path,
@visibleForTesting CrossFileTestOverrides overrides, @visibleForTesting CrossFileTestOverrides? overrides,
}) : super(path) { }) : super(path) {
throw UnimplementedError( throw UnimplementedError(
'CrossFile is not available in your current platform.'); 'CrossFile is not available in your current platform.');
@ -54,5 +54,5 @@ class CrossFileTestOverrides {
dynamic Function(String href, String suggestedName) createAnchorElement; dynamic Function(String href, String suggestedName) createAnchorElement;
/// Default constructor for overrides /// Default constructor for overrides
CrossFileTestOverrides({this.createAnchorElement}); CrossFileTestOverrides({required this.createAnchorElement});
} }

View File

@ -11,20 +11,20 @@ import './base.dart';
/// A CrossFile backed by a dart:io File. /// A CrossFile backed by a dart:io File.
class XFile extends XFileBase { class XFile extends XFileBase {
final File _file; final File _file;
final String mimeType; final String? mimeType;
final DateTime _lastModified; final DateTime? _lastModified;
int _length; int? _length;
final Uint8List _bytes; final Uint8List? _bytes;
/// Construct a CrossFile object backed by a dart:io File. /// Construct a CrossFile object backed by a dart:io File.
XFile( XFile(
String path, { String path, {
this.mimeType, this.mimeType,
String name, String? name,
int length, int? length,
Uint8List bytes, Uint8List? bytes,
DateTime lastModified, DateTime? lastModified,
}) : _file = File(path), }) : _file = File(path),
_bytes = null, _bytes = null,
_lastModified = lastModified, _lastModified = lastModified,
@ -34,10 +34,10 @@ class XFile extends XFileBase {
XFile.fromData( XFile.fromData(
Uint8List bytes, { Uint8List bytes, {
this.mimeType, this.mimeType,
String path, String? path,
String name, String? name,
int length, int? length,
DateTime lastModified, DateTime? lastModified,
}) : _bytes = bytes, }) : _bytes = bytes,
_file = File(path ?? ''), _file = File(path ?? ''),
_length = length, _length = length,
@ -84,7 +84,7 @@ class XFile extends XFileBase {
@override @override
Future<String> readAsString({Encoding encoding = utf8}) { Future<String> readAsString({Encoding encoding = utf8}) {
if (_bytes != null) { if (_bytes != null) {
return Future.value(String.fromCharCodes(_bytes)); return Future.value(String.fromCharCodes(_bytes!));
} }
return _file.readAsString(encoding: encoding); return _file.readAsString(encoding: encoding);
} }
@ -97,13 +97,13 @@ class XFile extends XFileBase {
return _file.readAsBytes(); return _file.readAsBytes();
} }
Stream<Uint8List> _getBytes(int start, int end) async* { Stream<Uint8List> _getBytes(int? start, int? end) async* {
final bytes = _bytes; final bytes = _bytes!;
yield bytes.sublist(start ?? 0, end ?? bytes.length); yield bytes.sublist(start ?? 0, end ?? bytes.length);
} }
@override @override
Stream<Uint8List> openRead([int start, int end]) { Stream<Uint8List> openRead([int? start, int? end]) {
if (_bytes != null) { if (_bytes != null) {
return _getBytes(start, end); return _getBytes(start, end);
} else { } else {

View File

@ -31,7 +31,7 @@ Element ensureInitialized(String id) {
if (target == null) { if (target == null) {
final Element targetElement = Element.tag('flt-x-file')..id = id; final Element targetElement = Element.tag('flt-x-file')..id = id;
querySelector('body').children.add(targetElement); querySelector('body')!.children.add(targetElement);
target = targetElement; target = targetElement;
} }
return target; return target;

View File

@ -1,19 +1,18 @@
name: cross_file name: cross_file
description: An abstraction to allow working with files across multiple platforms. description: An abstraction to allow working with files across multiple platforms.
homepage: https://github.com/flutter/plugins/tree/master/packages/cross_file homepage: https://github.com/flutter/plugins/tree/master/packages/cross_file
version: 0.2.1 version: 0.3.0-nullsafety
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
http: ^0.12.0+1 meta: ^1.3.0-nullsafety.3
meta: ^1.0.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pedantic: ^1.8.0 pedantic: ^1.10.0-nullsafety.3
environment: environment:
sdk: ">=2.1.0 <3.0.0" sdk: ">=2.12.0-0 <3.0.0"
flutter: ">=1.22.0" flutter: ">=1.22.0"

View File

@ -11,10 +11,8 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:cross_file/cross_file.dart'; import 'package:cross_file/cross_file.dart';
import 'dart:html';
final String expectedStringContents = 'Hello, world!'; 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 html.File textFile = html.File([bytes], 'hello.txt');
final String textFileUrl = html.Url.createObjectUrl(textFile); final String textFileUrl = html.Url.createObjectUrl(textFile);
@ -66,7 +64,7 @@ void main() {
await file.saveTo(''); await file.saveTo('');
final container = querySelector('#${CrossFileDomElementId}'); final container = html.querySelector('#${CrossFileDomElementId}');
expect(container, isNotNull); expect(container, isNotNull);
}); });
@ -76,18 +74,18 @@ void main() {
await file.saveTo('path'); await file.saveTo('path');
final container = querySelector('#${CrossFileDomElementId}'); final container = html.querySelector('#${CrossFileDomElementId}');
final AnchorElement element = container?.children?.firstWhere( final html.AnchorElement element =
(element) => element.tagName == 'A', container?.children.firstWhere((element) => element.tagName == 'A')
orElse: () => null); as html.AnchorElement;
expect(element, isNotNull); // if element is not found, the `firstWhere` call will throw StateError.
expect(element.href, file.path); expect(element.href, file.path);
expect(element.download, file.name); expect(element.download, file.name);
}); });
test('anchor element is clicked', () async { test('anchor element is clicked', () async {
final mockAnchor = AnchorElement(); final mockAnchor = html.AnchorElement();
CrossFileTestOverrides overrides = CrossFileTestOverrides( CrossFileTestOverrides overrides = CrossFileTestOverrides(
createAnchorElement: (_, __) => mockAnchor, createAnchorElement: (_, __) => mockAnchor,

View File

@ -24,7 +24,7 @@ import 'package:cross_file/cross_file.dart';
final pathPrefix = './assets/'; final pathPrefix = './assets/';
final path = pathPrefix + 'hello.txt'; final path = pathPrefix + 'hello.txt';
final String expectedStringContents = 'Hello, world!'; 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 File textFile = File(path);
final String textFilePath = textFile.path; final String textFilePath = textFile.path;