From 623c5156733b686797a60ae163ba17e2c7b2ffd3 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 18 Nov 2020 09:04:06 +0100 Subject: [PATCH 01/14] [cross_file] An abstraction to allow working with files across multiple platforms. (#3260) * Initial version of x_file package * Renamed from x_file to cross_file * Add back x_file type to file_selector * Fix formatting issues * Update homepage and version * Added README.md * Added missing copyright * Revert "Added missing copyright" This reverts commit cf7e8d5f3810ae646669f584738502a8cc3c5ca1. * Add missing copyright Co-Authored-By: Jason Panelli <38673809+jasonpanelli@users.noreply.github.com> * Renamed class implementation back to XFile * Fix formatting issues * Rename to cross_file * Added code owners for cross_file package Co-authored-by: Jason Panelli <38673809+jasonpanelli@users.noreply.github.com> --- CHANGELOG.md | 3 + LICENSE | 25 +++++ README.md | 34 +++++++ lib/cross_file.dart | 5 + lib/src/types/base.dart | 86 +++++++++++++++++ lib/src/types/html.dart | 136 +++++++++++++++++++++++++++ lib/src/types/interface.dart | 58 ++++++++++++ lib/src/types/io.dart | 115 ++++++++++++++++++++++ lib/src/web_helpers/web_helpers.dart | 38 ++++++++ lib/src/x_file.dart | 7 ++ pubspec.yaml | 20 ++++ test/assets/hello.txt | 1 + test/x_file_html_test.dart | 108 +++++++++++++++++++++ test/x_file_io_test.dart | 99 +++++++++++++++++++ 14 files changed, 735 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/cross_file.dart create mode 100644 lib/src/types/base.dart create mode 100644 lib/src/types/html.dart create mode 100644 lib/src/types/interface.dart create mode 100644 lib/src/types/io.dart create mode 100644 lib/src/web_helpers/web_helpers.dart create mode 100644 lib/src/x_file.dart create mode 100644 pubspec.yaml create mode 100644 test/assets/hello.txt create mode 100644 test/x_file_html_test.dart create mode 100644 test/x_file_io_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..3b5ae7756a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +- Initial open-source release \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..2c91f14381 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright 2020 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + 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 + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..f1ab89bc52 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# cross_file + +An abstraction to allow working with files across multiple platforms. + +# Usage + +Import `package:cross/cross_info.dart`, instantiate a `CrossFile` +using a path or byte array and use its methods and properties to +access the file and its metadata. + +Example: + +```dart +import 'package:cross_file/cross_file.dart'; + +final file = CrossFile('assets/hello.txt'); + +print('File information:'); +print('- Path: ${file.path}'); +print('- Name: ${file.name}'); +print('- MIME type: ${file.mimeType}'); + +final fileContent = await file.readAsString(); +print('Content of the file: ${fileContent}'); // e.g. "Moto G (4)" +``` + +You will find links to the API docs on the [pub page](https://pub.dartlang.org/packages/cross_file). + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). + +For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). \ No newline at end of file diff --git a/lib/cross_file.dart b/lib/cross_file.dart new file mode 100644 index 0000000000..a3e2873e67 --- /dev/null +++ b/lib/cross_file.dart @@ -0,0 +1,5 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/x_file.dart'; diff --git a/lib/src/types/base.dart b/lib/src/types/base.dart new file mode 100644 index 0000000000..6dc2d51b08 --- /dev/null +++ b/lib/src/types/base.dart @@ -0,0 +1,86 @@ +// Copyright 2018 The Chromium Authors. 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:convert'; +import 'dart:typed_data'; + +/// The interface for a CrossFile. +/// +/// A CrossFile is a container that wraps the path of a selected +/// file by the user and (in some platforms, like web) the bytes +/// with the contents of the file. +/// +/// This class is a very limited subset of dart:io [File], so all +/// the methods should seem familiar. +abstract class XFileBase { + /// Construct a CrossFile + XFileBase(String path); + + /// Save the CrossFile at the indicated file path. + void saveTo(String path) async { + throw UnimplementedError('saveTo has not been implemented.'); + } + + /// Get the path of the picked file. + /// + /// This should only be used as a backwards-compatibility clutch + /// for mobile apps, or cosmetic reasons only (to show the user + /// the path they've picked). + /// + /// 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 { + 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 { + throw UnimplementedError('.name has not been implemented.'); + } + + /// For web, it may be necessary for a file to know its MIME type. + String get mimeType { + throw UnimplementedError('.mimeType has not been implemented.'); + } + + /// Get the length of the file. Returns a `Future` that completes with the length in bytes. + Future length() { + throw UnimplementedError('.length() has not been implemented.'); + } + + /// Synchronously read the entire file contents as a string using the given [Encoding]. + /// + /// By default, `encoding` is [utf8]. + /// + /// Throws Exception if the operation fails. + Future readAsString({Encoding encoding = utf8}) { + throw UnimplementedError('readAsString() has not been implemented.'); + } + + /// Synchronously read the entire file contents as a list of bytes. + /// + /// Throws Exception if the operation fails. + Future readAsBytes() { + throw UnimplementedError('readAsBytes() has not been implemented.'); + } + + /// Create a new independent [Stream] for the contents of this file. + /// + /// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0). + /// + /// 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]) { + throw UnimplementedError('openRead() has not been implemented.'); + } + + /// Get the last-modified time for the CrossFile + Future lastModified() { + throw UnimplementedError('openRead() has not been implemented.'); + } +} diff --git a/lib/src/types/html.dart b/lib/src/types/html.dart new file mode 100644 index 0000000000..269f2a8d94 --- /dev/null +++ b/lib/src/types/html.dart @@ -0,0 +1,136 @@ +// Copyright 2018 The Chromium Authors. 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:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http show readBytes; +import 'package:meta/meta.dart'; +import 'dart:html'; + +import '../web_helpers/web_helpers.dart'; +import './base.dart'; + +/// A CrossFile that works on web. +/// +/// It wraps the bytes of a selected file. +class XFile extends XFileBase { + String path; + + final String mimeType; + final Uint8List _data; + final int _length; + final String name; + final DateTime _lastModified; + Element _target; + + final CrossFileTestOverrides _overrides; + + bool get _hasTestOverrides => _overrides != null; + + /// Construct a CrossFile object from its ObjectUrl. + /// + /// Optionally, this can be initialized with `bytes` and `length` + /// so no http requests are performed to retrieve files later. + /// + /// `name` needs to be passed from the outside, since we only have + /// access to it while we create the ObjectUrl. + XFile( + this.path, { + this.mimeType, + this.name, + int length, + Uint8List bytes, + DateTime lastModified, + @visibleForTesting CrossFileTestOverrides overrides, + }) : _data = bytes, + _length = length, + _overrides = overrides, + _lastModified = lastModified, + 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, + }) : _data = bytes, + _length = length, + _overrides = overrides, + _lastModified = lastModified, + super(path) { + if (path == null) { + final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType); + this.path = Url.createObjectUrl(blob); + } + } + + @override + Future lastModified() async { + if (_lastModified != null) { + return Future.value(_lastModified); + } + return null; + } + + Future get _bytes async { + if (_data != null) { + return Future.value(UnmodifiableUint8ListView(_data)); + } + return http.readBytes(path); + } + + @override + Future length() async { + return _length ?? (await _bytes).length; + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return encoding.decode(await _bytes); + } + + @override + Future readAsBytes() async { + return Future.value(await _bytes); + } + + @override + Stream openRead([int start, int end]) async* { + final bytes = await _bytes; + yield bytes.sublist(start ?? 0, end ?? bytes.length); + } + + /// Saves the data of this CrossFile at the location indicated by path. + /// For the web implementation, the path variable is ignored. + void saveTo(String path) async { + // Create a DOM container where we can host the anchor. + _target = ensureInitialized('__x_file_dom_element'); + + // 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); + + // Clear the children in our container so we can add an element to click + _target.children.clear(); + addElementToContainerAndClick(_target, element); + } +} + +/// Overrides some functions to allow testing +@visibleForTesting +class CrossFileTestOverrides { + /// For overriding the creation of the file input element. + Element Function(String href, String suggestedName) createAnchorElement; + + /// Default constructor for overrides + CrossFileTestOverrides({this.createAnchorElement}); +} diff --git a/lib/src/types/interface.dart b/lib/src/types/interface.dart new file mode 100644 index 0000000000..e30bc63b4c --- /dev/null +++ b/lib/src/types/interface.dart @@ -0,0 +1,58 @@ +// Copyright 2018 The Chromium Authors. 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:typed_data'; +import 'package:meta/meta.dart'; + +import './base.dart'; + +/// A CrossFile is a cross-platform, simplified File abstraction. +/// +/// It wraps the bytes of a selected file, and its (platform-dependant) path. +class XFile extends XFileBase { + /// Construct a CrossFile object from its path. + /// + /// Optionally, this can be initialized with `bytes` and `length` + /// so no http requests are performed to retrieve data later. + /// + /// `name` may be passed from the outside, for those cases where the effective + /// `path` of the file doesn't match what the user sees when selecting it + /// (like in web) + XFile( + String path, { + 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.'); + } + + /// Construct a CrossFile object from its data + XFile.fromData( + Uint8List bytes, { + 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.'); + } +} + +/// Overrides some functions of CrossFile for testing purposes +@visibleForTesting +class CrossFileTestOverrides { + /// For overriding the creation of the file input element. + dynamic Function(String href, String suggestedName) createAnchorElement; + + /// Default constructor for overrides + CrossFileTestOverrides({this.createAnchorElement}); +} diff --git a/lib/src/types/io.dart b/lib/src/types/io.dart new file mode 100644 index 0000000000..81b8cdd84d --- /dev/null +++ b/lib/src/types/io.dart @@ -0,0 +1,115 @@ +// Copyright 2018 The Chromium Authors. 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:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +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 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, + }) : _file = File(path), + _bytes = null, + _lastModified = lastModified, + super(path); + + /// Construct an CrossFile from its data + XFile.fromData( + Uint8List bytes, { + this.mimeType, + String path, + String name, + int length, + DateTime lastModified, + }) : _bytes = bytes, + _file = File(path ?? ''), + _length = length, + _lastModified = lastModified, + super(path) { + if (length == null) { + _length = bytes.length; + } + } + + @override + Future lastModified() { + if (_lastModified != null) { + return Future.value(_lastModified); + } + return _file.lastModified(); + } + + @override + void saveTo(String path) async { + File fileToSave = File(path); + await fileToSave.writeAsBytes(_bytes ?? (await readAsBytes())); + await fileToSave.create(); + } + + @override + String get path { + return _file.path; + } + + @override + String get name { + return _file.path.split(Platform.pathSeparator).last; + } + + @override + Future length() { + if (_length != null) { + return Future.value(_length); + } + return _file.length(); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + if (_bytes != null) { + return Future.value(String.fromCharCodes(_bytes)); + } + return _file.readAsString(encoding: encoding); + } + + @override + Future readAsBytes() { + if (_bytes != null) { + return Future.value(_bytes); + } + return _file.readAsBytes(); + } + + 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]) { + if (_bytes != null) { + return _getBytes(start, end); + } else { + return _file + .openRead(start ?? 0, end) + .map((chunk) => Uint8List.fromList(chunk)); + } + } +} diff --git a/lib/src/web_helpers/web_helpers.dart b/lib/src/web_helpers/web_helpers.dart new file mode 100644 index 0000000000..813f5f9755 --- /dev/null +++ b/lib/src/web_helpers/web_helpers.dart @@ -0,0 +1,38 @@ +// Copyright 2018 The Chromium Authors. 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:html'; + +/// Create anchor element with download attribute +AnchorElement createAnchorElement(String href, String suggestedName) { + final element = AnchorElement(href: href); + + if (suggestedName == null) { + element.download = 'download'; + } else { + element.download = suggestedName; + } + + return element; +} + +/// Add an element to a container and click it +void addElementToContainerAndClick(Element container, Element element) { + // Add the element and click it + // All previous elements will be removed before adding the new one + container.children.add(element); + element.click(); +} + +/// Initializes a DOM container where we can host elements. +Element ensureInitialized(String id) { + var target = querySelector('#${id}'); + if (target == null) { + final Element targetElement = Element.tag('flt-x-file')..id = id; + + querySelector('body').children.add(targetElement); + target = targetElement; + } + return target; +} diff --git a/lib/src/x_file.dart b/lib/src/x_file.dart new file mode 100644 index 0000000000..6136bff39f --- /dev/null +++ b/lib/src/x_file.dart @@ -0,0 +1,7 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'types/interface.dart' + if (dart.library.html) 'types/html.dart' + if (dart.library.io) 'types/io.dart'; diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000000..40084d3d1e --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,20 @@ + +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.1.0 + +dependencies: + flutter: + sdk: flutter + http: ^0.12.0+1 + meta: ^1.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + pedantic: ^1.8.0 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.22.0 <2.0.0" \ No newline at end of file diff --git a/test/assets/hello.txt b/test/assets/hello.txt new file mode 100644 index 0000000000..5dd01c177f --- /dev/null +++ b/test/assets/hello.txt @@ -0,0 +1 @@ +Hello, world! \ No newline at end of file diff --git a/test/x_file_html_test.dart b/test/x_file_html_test.dart new file mode 100644 index 0000000000..fadba96b3c --- /dev/null +++ b/test/x_file_html_test.dart @@ -0,0 +1,108 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('chrome') // Uses web-only Flutter SDK + +import 'dart:convert'; +import 'dart:html' as html; +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 html.File textFile = html.File([bytes], 'hello.txt'); +final String textFileUrl = html.Url.createObjectUrl(textFile); + +void main() { + group('Create with an objectUrl', () { + final file = XFile(textFileUrl); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); + + group('Create from data', () { + final file = XFile.fromData(bytes); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); + + group('saveTo(..)', () { + final String CrossFileDomElementId = '__x_file_dom_element'; + + group('CrossFile saveTo(..)', () { + test('creates a DOM container', () async { + XFile file = XFile.fromData(bytes); + + await file.saveTo(''); + + final container = querySelector('#${CrossFileDomElementId}'); + + expect(container, isNotNull); + }); + + test('create anchor element', () async { + XFile file = XFile.fromData(bytes, name: textFile.name); + + await file.saveTo('path'); + + final container = querySelector('#${CrossFileDomElementId}'); + final AnchorElement element = container?.children?.firstWhere( + (element) => element.tagName == 'A', + orElse: () => null); + + expect(element, isNotNull); + expect(element.href, file.path); + expect(element.download, file.name); + }); + + test('anchor element is clicked', () async { + final mockAnchor = AnchorElement(); + + CrossFileTestOverrides overrides = CrossFileTestOverrides( + createAnchorElement: (_, __) => mockAnchor, + ); + + XFile file = + XFile.fromData(bytes, name: textFile.name, overrides: overrides); + + bool clicked = false; + mockAnchor.onClick.listen((event) => clicked = true); + + await file.saveTo('path'); + + expect(clicked, true); + }); + }); + }); +} diff --git a/test/x_file_io_test.dart b/test/x_file_io_test.dart new file mode 100644 index 0000000000..65edea1ea4 --- /dev/null +++ b/test/x_file_io_test.dart @@ -0,0 +1,99 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('vm') // Uses dart:io + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:cross_file/cross_file.dart'; + +// Please note that executing this test with command +// `flutter test test/x_file_io_test.dart` will set the directory +// to ./file_selector_platform_interface. +// +// This will cause our hello.txt file to be not be found. Please +// execute this test with `flutter test` or change the path prefix +// to ./test/assets/ +// +// https://github.com/flutter/flutter/issues/20907 + +final pathPrefix = './assets/'; +final path = pathPrefix + 'hello.txt'; +final String expectedStringContents = 'Hello, world!'; +final Uint8List bytes = utf8.encode(expectedStringContents); +final File textFile = File(path); +final String textFilePath = textFile.path; + +void main() { + group('Create with a path', () { + final file = XFile(textFilePath); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + + test('saveTo(..) creates file', () async { + File removeBeforeTest = File(pathPrefix + 'newFilePath.txt'); + if (removeBeforeTest.existsSync()) { + await removeBeforeTest.delete(); + } + + await file.saveTo(pathPrefix + 'newFilePath.txt'); + File newFile = File(pathPrefix + 'newFilePath.txt'); + + expect(newFile.existsSync(), isTrue); + expect(newFile.readAsStringSync(), 'Hello, world!'); + + await newFile.delete(); + }); + }); + + group('Create with data', () { + final file = XFile.fromData(bytes); + + test('Can be read as a string', () async { + expect(await file.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await file.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await file.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + + test('Function saveTo(..) creates file', () async { + File removeBeforeTest = File(pathPrefix + 'newFileData.txt'); + if (removeBeforeTest.existsSync()) { + await removeBeforeTest.delete(); + } + + await file.saveTo(pathPrefix + 'newFileData.txt'); + File newFile = File(pathPrefix + 'newFileData.txt'); + + expect(newFile.existsSync(), isTrue); + expect(newFile.readAsStringSync(), 'Hello, world!'); + + await newFile.delete(); + }); + }); +} From 9e462852cb765e65495feb240c9677bd19fb73ef Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Sat, 12 Dec 2020 00:45:41 +0100 Subject: [PATCH 02/14] [a-c] Update Flutter SDK constraint (#3320) Update Flutter SDK constraint to match templates. --- CHANGELOG.md | 4 ++++ pubspec.yaml | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5ae7756a..b5d8a5695c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+1 + +- Update Flutter SDK constraint. + ## 0.1.0 - Initial open-source release \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 40084d3d1e..495a0ced5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,7 @@ - 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.1.0 +version: 0.1.0+1 dependencies: flutter: @@ -17,4 +16,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" \ No newline at end of file + flutter: ">=1.22.0" From 0b683e0d90c770dd43f11395fc548154982e96fd Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 18 Dec 2020 14:49:03 -0800 Subject: [PATCH 03/14] Fix outdated links across a number of markdown files (#3276) --- CHANGELOG.md | 4 ++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d8a5695c..1716cb5ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + ## 0.1.0+1 - Update Flutter SDK constraint. diff --git a/README.md b/README.md index f1ab89bc52..65bd418961 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ final fileContent = await file.readAsString(); print('Content of the file: ${fileContent}'); // e.g. "Moto G (4)" ``` -You will find links to the API docs on the [pub page](https://pub.dartlang.org/packages/cross_file). +You will find links to the API docs on the [pub page](https://pub.dev/packages/cross_file). ## Getting Started diff --git a/pubspec.yaml b/pubspec.yaml index 495a0ced5e..0c7f30677a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ 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.1.0+1 +version: 0.1.0+2 dependencies: flutter: From b5cdeb6adbb0b74752b6d929acb76edc5c6b0ff0 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 22 Dec 2020 19:02:49 +0100 Subject: [PATCH 04/14] Make sure saveTo returns a Future (#3363) --- CHANGELOG.md | 4 ++++ lib/src/types/base.dart | 2 +- lib/src/types/html.dart | 2 +- lib/src/types/io.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1716cb5ade..5ad91979dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +* **breaking change** Make sure the `saveTo` method returns a `Future` so it can be awaited and users are sure the file has been written to disk. + ## 0.1.0+2 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) diff --git a/lib/src/types/base.dart b/lib/src/types/base.dart index 6dc2d51b08..1a1b5694d5 100644 --- a/lib/src/types/base.dart +++ b/lib/src/types/base.dart @@ -18,7 +18,7 @@ abstract class XFileBase { XFileBase(String path); /// Save the CrossFile at the indicated file path. - void saveTo(String path) async { + Future saveTo(String path) { throw UnimplementedError('saveTo has not been implemented.'); } diff --git a/lib/src/types/html.dart b/lib/src/types/html.dart index 269f2a8d94..646939612d 100644 --- a/lib/src/types/html.dart +++ b/lib/src/types/html.dart @@ -108,7 +108,7 @@ class XFile extends XFileBase { /// Saves the data of this CrossFile at the location indicated by path. /// For the web implementation, the path variable is ignored. - void saveTo(String path) async { + Future saveTo(String path) async { // Create a DOM container where we can host the anchor. _target = ensureInitialized('__x_file_dom_element'); diff --git a/lib/src/types/io.dart b/lib/src/types/io.dart index 81b8cdd84d..d9a93559b5 100644 --- a/lib/src/types/io.dart +++ b/lib/src/types/io.dart @@ -57,7 +57,7 @@ class XFile extends XFileBase { } @override - void saveTo(String path) async { + Future saveTo(String path) async { File fileToSave = File(path); await fileToSave.writeAsBytes(_bytes ?? (await readAsBytes())); await fileToSave.create(); diff --git a/pubspec.yaml b/pubspec.yaml index 0c7f30677a..4c9acf9b00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ 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.1.0+2 +version: 0.2.0 dependencies: flutter: From 52579675a0dc905ba8d96fb4c93d5cdf5d3723c4 Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Mon, 25 Jan 2021 17:33:19 -0800 Subject: [PATCH 05/14] [cross_file] Use Uri when calling package:http methods (#3462) The next version of package:http expects URIs. See dart-lang/http#507 --- CHANGELOG.md | 6 +++++- lib/src/types/html.dart | 6 +++--- pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad91979dc..45f516ad33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1 + +* Prepare for breaking `package:http` change. + ## 0.2.0 * **breaking change** Make sure the `saveTo` method returns a `Future` so it can be awaited and users are sure the file has been written to disk. @@ -12,4 +16,4 @@ ## 0.1.0 -- Initial open-source release \ No newline at end of file +- Initial open-source release diff --git a/lib/src/types/html.dart b/lib/src/types/html.dart index 646939612d..527d5e6911 100644 --- a/lib/src/types/html.dart +++ b/lib/src/types/html.dart @@ -3,14 +3,14 @@ // found in the LICENSE file. 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 'dart:html'; -import '../web_helpers/web_helpers.dart'; import './base.dart'; +import '../web_helpers/web_helpers.dart'; /// A CrossFile that works on web. /// @@ -82,7 +82,7 @@ class XFile extends XFileBase { if (_data != null) { return Future.value(UnmodifiableUint8ListView(_data)); } - return http.readBytes(path); + return http.readBytes(Uri.parse(path)); } @override diff --git a/pubspec.yaml b/pubspec.yaml index 4c9acf9b00..2228674baf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ 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.0 +version: 0.2.1 dependencies: flutter: From 519abf9b5bc5d325d97cf4cd06236233d76a2f0f Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Jan 2021 11:16:40 -0800 Subject: [PATCH 06/14] [cross_file] Migrate to null-safety. (#3452) Also: skip some packages from the all_plugins app so CI passes. --- CHANGELOG.md | 12 +++-- lib/src/types/base.dart | 10 ++-- lib/src/types/html.dart | 79 ++++++++++++++-------------- lib/src/types/interface.dart | 26 ++++----- lib/src/types/io.dart | 32 +++++------ lib/src/web_helpers/web_helpers.dart | 2 +- pubspec.yaml | 9 ++-- test/x_file_html_test.dart | 18 +++---- test/x_file_io_test.dart | 2 +- 9 files changed, 96 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f516ad33..c9b3d1ab25 100644 --- a/CHANGELOG.md +++ b/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/lib/src/types/base.dart b/lib/src/types/base.dart index 1a1b5694d5..2a59c1c2b2 100644 --- a/lib/src/types/base.dart +++ b/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/lib/src/types/html.dart b/lib/src/types/html.dart index 527d5e6911..203ab5d82e 100644 --- a/lib/src/types/html.dart +++ b/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/lib/src/types/interface.dart b/lib/src/types/interface.dart index e30bc63b4c..122f3d1d93 100644 --- a/lib/src/types/interface.dart +++ b/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/lib/src/types/io.dart b/lib/src/types/io.dart index d9a93559b5..6eafaf0ce0 100644 --- a/lib/src/types/io.dart +++ b/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/lib/src/web_helpers/web_helpers.dart b/lib/src/web_helpers/web_helpers.dart index 813f5f9755..a963e9933f 100644 --- a/lib/src/web_helpers/web_helpers.dart +++ b/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/pubspec.yaml b/pubspec.yaml index 2228674baf..af1b7e7d4c 100644 --- a/pubspec.yaml +++ b/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/test/x_file_html_test.dart b/test/x_file_html_test.dart index fadba96b3c..a271aa1f15 100644 --- a/test/x_file_html_test.dart +++ b/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/test/x_file_io_test.dart b/test/x_file_io_test.dart index 65edea1ea4..25f46a4eda 100644 --- a/test/x_file_io_test.dart +++ b/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; From 897887f82e7bc6a31bdcdaa30eebbbb2f95cac42 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 26 Jan 2021 14:17:07 -0800 Subject: [PATCH 07/14] [image_picker_platform_interface] fix test asset file location (#3467) --- test/x_file_io_test.dart | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/x_file_io_test.dart b/test/x_file_io_test.dart index 25f46a4eda..94ac81c4ca 100644 --- a/test/x_file_io_test.dart +++ b/test/x_file_io_test.dart @@ -11,17 +11,8 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:cross_file/cross_file.dart'; -// Please note that executing this test with command -// `flutter test test/x_file_io_test.dart` will set the directory -// to ./file_selector_platform_interface. -// -// This will cause our hello.txt file to be not be found. Please -// execute this test with `flutter test` or change the path prefix -// to ./test/assets/ -// -// https://github.com/flutter/flutter/issues/20907 - -final pathPrefix = './assets/'; +final pathPrefix = + Directory.current.path.endsWith('test') ? './assets/' : './test/assets/'; final path = pathPrefix + 'hello.txt'; final String expectedStringContents = 'Hello, world!'; final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); @@ -30,7 +21,7 @@ final String textFilePath = textFile.path; void main() { group('Create with a path', () { - final file = XFile(textFilePath); + final XFile file = XFile(textFilePath); test('Can be read as a string', () async { expect(await file.readAsString(), equals(expectedStringContents)); From d7cf598bb64c3aeb47b04da123eacd530dfa89b4 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Jan 2021 17:33:26 -0800 Subject: [PATCH 08/14] Revert "[cross_file] Migrate to null-safety. (#3452)" (#3468) This reverts commit ca9921196a7ae96edad91a9cd7b7d8fe9f5689ff. --- CHANGELOG.md | 12 ++--- lib/src/types/base.dart | 10 ++-- lib/src/types/html.dart | 79 ++++++++++++++-------------- lib/src/types/interface.dart | 26 ++++----- lib/src/types/io.dart | 32 +++++------ lib/src/web_helpers/web_helpers.dart | 2 +- pubspec.yaml | 9 ++-- test/x_file_html_test.dart | 18 ++++--- test/x_file_io_test.dart | 2 +- 9 files changed, 94 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b3d1ab25..45f516ad33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,6 @@ -## 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 @@ -18,8 +12,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/lib/src/types/base.dart b/lib/src/types/base.dart index 2a59c1c2b2..1a1b5694d5 100644 --- a/lib/src/types/base.dart +++ b/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/lib/src/types/html.dart b/lib/src/types/html.dart index 203ab5d82e..527d5e6911 100644 --- a/lib/src/types/html.dart +++ b/lib/src/types/html.dart @@ -6,6 +6,7 @@ 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'; @@ -15,17 +16,16 @@ import '../web_helpers/web_helpers.dart'; /// /// It wraps the bytes of a selected file. class XFile extends XFileBase { - late String path; + 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; + final DateTime _lastModified; + Element _target; - late Element _target; - - final CrossFileTestOverrides? _overrides; + final CrossFileTestOverrides _overrides; bool get _hasTestOverrides => _overrides != null; @@ -39,58 +39,56 @@ class XFile extends XFileBase { XFile( this.path, { this.mimeType, - String? name, - int? length, - Uint8List? bytes, - DateTime? lastModified, - @visibleForTesting CrossFileTestOverrides? overrides, + this.name, + int length, + Uint8List bytes, + DateTime lastModified, + @visibleForTesting CrossFileTestOverrides overrides, }) : _data = bytes, _length = length, _overrides = overrides, - _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), - name = name ?? '', + _lastModified = lastModified, super(path); /// Construct an CrossFile from its data XFile.fromData( Uint8List bytes, { this.mimeType, - String? name, - int? length, - DateTime? lastModified, - String? path, - @visibleForTesting CrossFileTestOverrides? overrides, + this.name, + int length, + DateTime lastModified, + this.path, + @visibleForTesting CrossFileTestOverrides overrides, }) : _data = bytes, _length = length, _overrides = overrides, - _lastModified = lastModified ?? DateTime.fromMillisecondsSinceEpoch(0), - name = name ?? '', + _lastModified = lastModified, 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 => Future.value(_lastModified); + Future lastModified() async { + if (_lastModified != null) { + return Future.value(_lastModified); + } + return null; + } Future get _bytes async { if (_data != null) { - return Future.value(UnmodifiableUint8ListView(_data!)); + return Future.value(UnmodifiableUint8ListView(_data)); } - - // 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); + return http.readBytes(Uri.parse(path)); } @override - Future length() async => _length ?? (await _bytes).length; + Future length() async { + return _length ?? (await _bytes).length; + } @override Future readAsString({Encoding encoding = utf8}) async { @@ -98,10 +96,12 @@ class XFile extends XFileBase { } @override - Future readAsBytes() async => Future.value(await _bytes); + Future readAsBytes() async { + return 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,9 +114,10 @@ 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(this.path, this.name) as AnchorElement - : createAnchorElement(this.path, this.name); + final AnchorElement element = + (_hasTestOverrides && _overrides.createAnchorElement != null) + ? _overrides.createAnchorElement(this.path, this.name) + : createAnchorElement(this.path, this.name); // Clear the children in our container so we can add an element to click _target.children.clear(); @@ -131,5 +132,5 @@ class CrossFileTestOverrides { Element Function(String href, String suggestedName) createAnchorElement; /// Default constructor for overrides - CrossFileTestOverrides({required this.createAnchorElement}); + CrossFileTestOverrides({this.createAnchorElement}); } diff --git a/lib/src/types/interface.dart b/lib/src/types/interface.dart index 122f3d1d93..e30bc63b4c 100644 --- a/lib/src/types/interface.dart +++ b/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({required this.createAnchorElement}); + CrossFileTestOverrides({this.createAnchorElement}); } diff --git a/lib/src/types/io.dart b/lib/src/types/io.dart index 6eafaf0ce0..d9a93559b5 100644 --- a/lib/src/types/io.dart +++ b/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/lib/src/web_helpers/web_helpers.dart b/lib/src/web_helpers/web_helpers.dart index a963e9933f..813f5f9755 100644 --- a/lib/src/web_helpers/web_helpers.dart +++ b/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/pubspec.yaml b/pubspec.yaml index af1b7e7d4c..2228674baf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +1,19 @@ 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.3.0-nullsafety +version: 0.2.1 dependencies: flutter: sdk: flutter - meta: ^1.3.0-nullsafety.3 + http: ^0.12.0+1 + meta: ^1.0.5 dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.10.0-nullsafety.3 + pedantic: ^1.8.0 environment: - sdk: ">=2.12.0-0 <3.0.0" + sdk: ">=2.1.0 <3.0.0" flutter: ">=1.22.0" diff --git a/test/x_file_html_test.dart b/test/x_file_html_test.dart index a271aa1f15..fadba96b3c 100644 --- a/test/x_file_html_test.dart +++ b/test/x_file_html_test.dart @@ -11,8 +11,10 @@ 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 = Uint8List.fromList(utf8.encode(expectedStringContents)); +final Uint8List bytes = utf8.encode(expectedStringContents); final html.File textFile = html.File([bytes], 'hello.txt'); final String textFileUrl = html.Url.createObjectUrl(textFile); @@ -64,7 +66,7 @@ void main() { await file.saveTo(''); - final container = html.querySelector('#${CrossFileDomElementId}'); + final container = querySelector('#${CrossFileDomElementId}'); expect(container, isNotNull); }); @@ -74,18 +76,18 @@ void main() { await file.saveTo('path'); - final container = html.querySelector('#${CrossFileDomElementId}'); - final html.AnchorElement element = - container?.children.firstWhere((element) => element.tagName == 'A') - as html.AnchorElement; + final container = querySelector('#${CrossFileDomElementId}'); + final AnchorElement element = container?.children?.firstWhere( + (element) => element.tagName == 'A', + orElse: () => null); - // if element is not found, the `firstWhere` call will throw StateError. + expect(element, isNotNull); expect(element.href, file.path); expect(element.download, file.name); }); test('anchor element is clicked', () async { - final mockAnchor = html.AnchorElement(); + final mockAnchor = AnchorElement(); CrossFileTestOverrides overrides = CrossFileTestOverrides( createAnchorElement: (_, __) => mockAnchor, diff --git a/test/x_file_io_test.dart b/test/x_file_io_test.dart index 94ac81c4ca..d45ff599fe 100644 --- a/test/x_file_io_test.dart +++ b/test/x_file_io_test.dart @@ -15,7 +15,7 @@ final pathPrefix = Directory.current.path.endsWith('test') ? './assets/' : './test/assets/'; final path = pathPrefix + 'hello.txt'; final String expectedStringContents = 'Hello, world!'; -final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); +final Uint8List bytes = utf8.encode(expectedStringContents); final File textFile = File(path); final String textFilePath = textFile.path; From e67f7f2a1efce1676499f35ab6934ecb6f20ef3b Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Jan 2021 18:14:52 -0800 Subject: [PATCH 09/14] Reland "[cross_file] Migrate to null-safety. (#3452)" (#3469) This reverts commit 4aacf970f3891c9cf23c768ca1c8bec04c42b35a. --- CHANGELOG.md | 12 +++-- lib/src/types/base.dart | 10 ++-- lib/src/types/html.dart | 79 ++++++++++++++-------------- lib/src/types/interface.dart | 26 ++++----- lib/src/types/io.dart | 32 +++++------ lib/src/web_helpers/web_helpers.dart | 2 +- pubspec.yaml | 9 ++-- test/x_file_html_test.dart | 18 +++---- test/x_file_io_test.dart | 2 +- 9 files changed, 96 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f516ad33..c9b3d1ab25 100644 --- a/CHANGELOG.md +++ b/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/lib/src/types/base.dart b/lib/src/types/base.dart index 1a1b5694d5..2a59c1c2b2 100644 --- a/lib/src/types/base.dart +++ b/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/lib/src/types/html.dart b/lib/src/types/html.dart index 527d5e6911..203ab5d82e 100644 --- a/lib/src/types/html.dart +++ b/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/lib/src/types/interface.dart b/lib/src/types/interface.dart index e30bc63b4c..122f3d1d93 100644 --- a/lib/src/types/interface.dart +++ b/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/lib/src/types/io.dart b/lib/src/types/io.dart index d9a93559b5..6eafaf0ce0 100644 --- a/lib/src/types/io.dart +++ b/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/lib/src/web_helpers/web_helpers.dart b/lib/src/web_helpers/web_helpers.dart index 813f5f9755..a963e9933f 100644 --- a/lib/src/web_helpers/web_helpers.dart +++ b/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/pubspec.yaml b/pubspec.yaml index 2228674baf..af1b7e7d4c 100644 --- a/pubspec.yaml +++ b/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/test/x_file_html_test.dart b/test/x_file_html_test.dart index fadba96b3c..a271aa1f15 100644 --- a/test/x_file_html_test.dart +++ b/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/test/x_file_io_test.dart b/test/x_file_io_test.dart index d45ff599fe..94ac81c4ca 100644 --- a/test/x_file_io_test.dart +++ b/test/x_file_io_test.dart @@ -15,7 +15,7 @@ final pathPrefix = 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; From a826d645724bc1258be16349b6f8c21de74e322d Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 22 Feb 2021 19:48:31 +0100 Subject: [PATCH 10/14] [cross_file] Stable null safety release (#3593) --- CHANGELOG.md | 2 +- pubspec.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b3d1ab25..5bbb43f9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.3.0-nullsafety +## 0.3.0 * Migrated package to null-safety. * **breaking change** According to our unit tests, the API should be backwards-compatible. Some relevant changes were made, however: diff --git a/pubspec.yaml b/pubspec.yaml index af1b7e7d4c..8e09b21d45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +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.3.0-nullsafety +version: 0.3.0 dependencies: flutter: sdk: flutter - meta: ^1.3.0-nullsafety.3 + meta: ^1.3.0 dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.10.0-nullsafety.3 + pedantic: ^1.10.0 environment: - sdk: ">=2.12.0-0 <3.0.0" + sdk: ">=2.12.0-259.9.beta <3.0.0" flutter: ">=1.22.0" From de5699c6b261272eb390d86f0a1d9fee567e79e6 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 24 Feb 2021 20:17:34 -0800 Subject: [PATCH 11/14] [cross_file] Fix base class nullability (#3629) Without this, the dummy ("interface") XFile implementation of these properties has different nullability than the others, and the analyzer doesn't match what the runtime actually sees. --- CHANGELOG.md | 5 +++++ lib/src/types/base.dart | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bbb43f9e8..94bf4b2932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.1 + +* Fix nullability of `XFileBase`'s `path` and `name` to match the + implementations to avoid potential analyzer issues. + ## 0.3.0 * Migrated package to null-safety. diff --git a/lib/src/types/base.dart b/lib/src/types/base.dart index 2a59c1c2b2..4522b7343c 100644 --- a/lib/src/types/base.dart +++ b/lib/src/types/base.dart @@ -31,14 +31,14 @@ 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.'); } diff --git a/pubspec.yaml b/pubspec.yaml index 8e09b21d45..66d3f46a84 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ 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.3.0 +version: 0.3.1 dependencies: flutter: From 807011c535daed0fe54968a5f3c921879da4ef15 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 9 Mar 2021 14:39:57 -0800 Subject: [PATCH 12/14] Move cross_file to packages. --- CHANGELOG.md => packages/cross_file/CHANGELOG.md | 0 LICENSE => packages/cross_file/LICENSE | 0 README.md => packages/cross_file/README.md | 0 {lib => packages/cross_file/lib}/cross_file.dart | 0 {lib => packages/cross_file/lib}/src/types/base.dart | 0 {lib => packages/cross_file/lib}/src/types/html.dart | 0 {lib => packages/cross_file/lib}/src/types/interface.dart | 0 {lib => packages/cross_file/lib}/src/types/io.dart | 0 {lib => packages/cross_file/lib}/src/web_helpers/web_helpers.dart | 0 {lib => packages/cross_file/lib}/src/x_file.dart | 0 pubspec.yaml => packages/cross_file/pubspec.yaml | 0 {test => packages/cross_file/test}/assets/hello.txt | 0 {test => packages/cross_file/test}/x_file_html_test.dart | 0 {test => packages/cross_file/test}/x_file_io_test.dart | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG.md => packages/cross_file/CHANGELOG.md (100%) rename LICENSE => packages/cross_file/LICENSE (100%) rename README.md => packages/cross_file/README.md (100%) rename {lib => packages/cross_file/lib}/cross_file.dart (100%) rename {lib => packages/cross_file/lib}/src/types/base.dart (100%) rename {lib => packages/cross_file/lib}/src/types/html.dart (100%) rename {lib => packages/cross_file/lib}/src/types/interface.dart (100%) rename {lib => packages/cross_file/lib}/src/types/io.dart (100%) rename {lib => packages/cross_file/lib}/src/web_helpers/web_helpers.dart (100%) rename {lib => packages/cross_file/lib}/src/x_file.dart (100%) rename pubspec.yaml => packages/cross_file/pubspec.yaml (100%) rename {test => packages/cross_file/test}/assets/hello.txt (100%) rename {test => packages/cross_file/test}/x_file_html_test.dart (100%) rename {test => packages/cross_file/test}/x_file_io_test.dart (100%) diff --git a/CHANGELOG.md b/packages/cross_file/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/cross_file/CHANGELOG.md diff --git a/LICENSE b/packages/cross_file/LICENSE similarity index 100% rename from LICENSE rename to packages/cross_file/LICENSE diff --git a/README.md b/packages/cross_file/README.md similarity index 100% rename from README.md rename to packages/cross_file/README.md diff --git a/lib/cross_file.dart b/packages/cross_file/lib/cross_file.dart similarity index 100% rename from lib/cross_file.dart rename to packages/cross_file/lib/cross_file.dart diff --git a/lib/src/types/base.dart b/packages/cross_file/lib/src/types/base.dart similarity index 100% rename from lib/src/types/base.dart rename to packages/cross_file/lib/src/types/base.dart diff --git a/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart similarity index 100% rename from lib/src/types/html.dart rename to packages/cross_file/lib/src/types/html.dart diff --git a/lib/src/types/interface.dart b/packages/cross_file/lib/src/types/interface.dart similarity index 100% rename from lib/src/types/interface.dart rename to packages/cross_file/lib/src/types/interface.dart diff --git a/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart similarity index 100% rename from lib/src/types/io.dart rename to packages/cross_file/lib/src/types/io.dart diff --git a/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart similarity index 100% rename from lib/src/web_helpers/web_helpers.dart rename to packages/cross_file/lib/src/web_helpers/web_helpers.dart diff --git a/lib/src/x_file.dart b/packages/cross_file/lib/src/x_file.dart similarity index 100% rename from lib/src/x_file.dart rename to packages/cross_file/lib/src/x_file.dart diff --git a/pubspec.yaml b/packages/cross_file/pubspec.yaml similarity index 100% rename from pubspec.yaml rename to packages/cross_file/pubspec.yaml diff --git a/test/assets/hello.txt b/packages/cross_file/test/assets/hello.txt similarity index 100% rename from test/assets/hello.txt rename to packages/cross_file/test/assets/hello.txt diff --git a/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart similarity index 100% rename from test/x_file_html_test.dart rename to packages/cross_file/test/x_file_html_test.dart diff --git a/test/x_file_io_test.dart b/packages/cross_file/test/x_file_io_test.dart similarity index 100% rename from test/x_file_io_test.dart rename to packages/cross_file/test/x_file_io_test.dart From be6f3d096c4ab3aa083c7164e640c27c6ca698aa Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 9 Mar 2021 16:53:28 -0800 Subject: [PATCH 13/14] Make analyzer happy. Tests pass. --- packages/cross_file/lib/src/types/base.dart | 1 + packages/cross_file/lib/src/types/html.dart | 60 +++++++++++-------- .../cross_file/lib/src/types/interface.dart | 8 ++- packages/cross_file/lib/src/types/io.dart | 32 +++++----- .../lib/src/web_helpers/web_helpers.dart | 6 +- .../cross_file/test/x_file_html_test.dart | 34 ++++++----- packages/cross_file/test/x_file_io_test.dart | 16 ++--- 7 files changed, 87 insertions(+), 70 deletions(-) diff --git a/packages/cross_file/lib/src/types/base.dart b/packages/cross_file/lib/src/types/base.dart index 4522b7343c..98c2f8c358 100644 --- a/packages/cross_file/lib/src/types/base.dart +++ b/packages/cross_file/lib/src/types/base.dart @@ -15,6 +15,7 @@ import 'dart:typed_data'; /// the methods should seem familiar. abstract class XFileBase { /// Construct a CrossFile + // ignore: avoid_unused_constructor_parameters XFileBase(String? path); /// Save the CrossFile at the indicated file path. diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index 203ab5d82e..ef69af59fa 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -8,27 +8,13 @@ import 'dart:typed_data'; import 'package:meta/meta.dart'; -import './base.dart'; import '../web_helpers/web_helpers.dart'; +import './base.dart'; /// A CrossFile that works on web. /// /// It wraps the bytes of a selected file. class XFile extends XFileBase { - late String path; - - final String? mimeType; - final Uint8List? _data; - final int? _length; - final String name; - final DateTime? _lastModified; - - late Element _target; - - final CrossFileTestOverrides? _overrides; - - bool get _hasTestOverrides => _overrides != null; - /// Construct a CrossFile object from its ObjectUrl. /// /// Optionally, this can be initialized with `bytes` and `length` @@ -67,7 +53,9 @@ class XFile extends XFileBase { name = name ?? '', super(path) { if (path == null) { - final blob = (mimeType == null) ? Blob([bytes]) : Blob([bytes], mimeType); + final Blob blob = (mimeType == null) + ? Blob([bytes]) + : Blob([bytes], mimeType); this.path = Url.createObjectUrl(blob); } else { this.path = path; @@ -75,15 +63,33 @@ class XFile extends XFileBase { } @override - Future lastModified() async => Future.value(_lastModified); + final String? mimeType; + @override + final String name; + @override + late String path; + + final Uint8List? _data; + final int? _length; + final DateTime? _lastModified; + + late Element _target; + + final CrossFileTestOverrides? _overrides; + + bool get _hasTestOverrides => _overrides != null; + + @override + Future lastModified() async => + Future.value(_lastModified); Future get _bytes async { if (_data != null) { - return Future.value(UnmodifiableUint8ListView(_data!)); + return Future.value(UnmodifiableUint8ListView(_data!)); } // We can force 'response' to be a byte buffer by passing responseType: - ByteBuffer? response = + final ByteBuffer? response = (await HttpRequest.request(path, responseType: 'arraybuffer')).response; return response?.asUint8List() ?? Uint8List(0); @@ -98,16 +104,18 @@ class XFile extends XFileBase { } @override - Future readAsBytes() async => Future.value(await _bytes); + Future readAsBytes() async => + Future.value(await _bytes); @override Stream openRead([int? start, int? end]) async* { - final bytes = await _bytes; + final Uint8List bytes = await _bytes; yield bytes.sublist(start ?? 0, end ?? bytes.length); } /// Saves the data of this CrossFile at the location indicated by path. /// For the web implementation, the path variable is ignored. + @override Future saveTo(String path) async { // Create a DOM container where we can host the anchor. _target = ensureInitialized('__x_file_dom_element'); @@ -115,8 +123,8 @@ 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(this.path, this.name) as AnchorElement - : createAnchorElement(this.path, this.name); + ? _overrides!.createAnchorElement(this.path, name) as AnchorElement + : createAnchorElement(this.path, name); // Clear the children in our container so we can add an element to click _target.children.clear(); @@ -127,9 +135,9 @@ class XFile extends XFileBase { /// Overrides some functions to allow testing @visibleForTesting class CrossFileTestOverrides { - /// For overriding the creation of the file input element. - Element Function(String href, String suggestedName) createAnchorElement; - /// Default constructor for overrides CrossFileTestOverrides({required this.createAnchorElement}); + + /// For overriding the creation of the file input element. + Element Function(String href, String suggestedName) createAnchorElement; } diff --git a/packages/cross_file/lib/src/types/interface.dart b/packages/cross_file/lib/src/types/interface.dart index 122f3d1d93..91afac5d42 100644 --- a/packages/cross_file/lib/src/types/interface.dart +++ b/packages/cross_file/lib/src/types/interface.dart @@ -7,6 +7,8 @@ import 'package:meta/meta.dart'; import './base.dart'; +// ignore_for_file: avoid_unused_constructor_parameters + /// A CrossFile is a cross-platform, simplified File abstraction. /// /// It wraps the bytes of a selected file, and its (platform-dependant) path. @@ -50,9 +52,9 @@ class XFile extends XFileBase { /// Overrides some functions of CrossFile for testing purposes @visibleForTesting class CrossFileTestOverrides { - /// For overriding the creation of the file input element. - dynamic Function(String href, String suggestedName) createAnchorElement; - /// Default constructor for overrides CrossFileTestOverrides({required this.createAnchorElement}); + + /// For overriding the creation of the file input element. + dynamic Function(String href, String suggestedName) createAnchorElement; } diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart index 6eafaf0ce0..6d649ced82 100644 --- a/packages/cross_file/lib/src/types/io.dart +++ b/packages/cross_file/lib/src/types/io.dart @@ -8,15 +8,10 @@ import 'dart:typed_data'; import './base.dart'; +// ignore_for_file: avoid_unused_constructor_parameters + /// A CrossFile backed by a dart:io File. class XFile extends XFileBase { - final File _file; - final String? mimeType; - final DateTime? _lastModified; - int? _length; - - final Uint8List? _bytes; - /// Construct a CrossFile object backed by a dart:io File. XFile( String path, { @@ -48,17 +43,26 @@ class XFile extends XFileBase { } } + final File _file; + @override + final String? mimeType; + final DateTime? _lastModified; + int? _length; + + final Uint8List? _bytes; + @override Future lastModified() { if (_lastModified != null) { - return Future.value(_lastModified); + return Future.value(_lastModified); } + // ignore: avoid_slow_async_io return _file.lastModified(); } @override Future saveTo(String path) async { - File fileToSave = File(path); + final File fileToSave = File(path); await fileToSave.writeAsBytes(_bytes ?? (await readAsBytes())); await fileToSave.create(); } @@ -76,7 +80,7 @@ class XFile extends XFileBase { @override Future length() { if (_length != null) { - return Future.value(_length); + return Future.value(_length); } return _file.length(); } @@ -84,7 +88,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); } @@ -92,13 +96,13 @@ class XFile extends XFileBase { @override Future readAsBytes() { if (_bytes != null) { - return Future.value(_bytes); + return Future.value(_bytes); } return _file.readAsBytes(); } Stream _getBytes(int? start, int? end) async* { - final bytes = _bytes!; + final Uint8List bytes = _bytes!; yield bytes.sublist(start ?? 0, end ?? bytes.length); } @@ -109,7 +113,7 @@ class XFile extends XFileBase { } else { return _file .openRead(start ?? 0, end) - .map((chunk) => Uint8List.fromList(chunk)); + .map((List chunk) => Uint8List.fromList(chunk)); } } } 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 a963e9933f..9440d8a0e5 100644 --- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart +++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart @@ -5,8 +5,8 @@ import 'dart:html'; /// Create anchor element with download attribute -AnchorElement createAnchorElement(String href, String suggestedName) { - final element = AnchorElement(href: href); +AnchorElement createAnchorElement(String href, String? suggestedName) { + final AnchorElement element = AnchorElement(href: href); if (suggestedName == null) { element.download = 'download'; @@ -27,7 +27,7 @@ void addElementToContainerAndClick(Element container, Element element) { /// Initializes a DOM container where we can host elements. Element ensureInitialized(String id) { - var target = querySelector('#${id}'); + Element? target = querySelector('#$id'); if (target == null) { final Element targetElement = Element.tag('flt-x-file')..id = id; diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart index a271aa1f15..43740f0f19 100644 --- a/packages/cross_file/test/x_file_html_test.dart +++ b/packages/cross_file/test/x_file_html_test.dart @@ -11,14 +11,14 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:cross_file/cross_file.dart'; -final String expectedStringContents = 'Hello, world!'; +const String expectedStringContents = 'Hello, world!'; 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); void main() { group('Create with an objectUrl', () { - final file = XFile(textFileUrl); + final XFile file = XFile(textFileUrl); test('Can be read as a string', () async { expect(await file.readAsString(), equals(expectedStringContents)); @@ -37,7 +37,7 @@ void main() { }); group('Create from data', () { - final file = XFile.fromData(bytes); + final XFile file = XFile.fromData(bytes); test('Can be read as a string', () async { expect(await file.readAsString(), equals(expectedStringContents)); @@ -56,28 +56,30 @@ void main() { }); group('saveTo(..)', () { - final String CrossFileDomElementId = '__x_file_dom_element'; + const String crossFileDomElementId = '__x_file_dom_element'; group('CrossFile saveTo(..)', () { test('creates a DOM container', () async { - XFile file = XFile.fromData(bytes); + final XFile file = XFile.fromData(bytes); await file.saveTo(''); - final container = html.querySelector('#${CrossFileDomElementId}'); + final html.Element? container = + html.querySelector('#$crossFileDomElementId'); expect(container, isNotNull); }); test('create anchor element', () async { - XFile file = XFile.fromData(bytes, name: textFile.name); + final XFile file = XFile.fromData(bytes, name: textFile.name); await file.saveTo('path'); - final container = html.querySelector('#${CrossFileDomElementId}'); - final html.AnchorElement element = - container?.children.firstWhere((element) => element.tagName == 'A') - as html.AnchorElement; + final html.Element? container = + html.querySelector('#$crossFileDomElementId'); + final html.AnchorElement element = container?.children + .firstWhere((html.Element element) => element.tagName == 'A') + as html.AnchorElement; // if element is not found, the `firstWhere` call will throw StateError. expect(element.href, file.path); @@ -85,17 +87,17 @@ void main() { }); test('anchor element is clicked', () async { - final mockAnchor = html.AnchorElement(); + final html.AnchorElement mockAnchor = html.AnchorElement(); - CrossFileTestOverrides overrides = CrossFileTestOverrides( + final CrossFileTestOverrides overrides = CrossFileTestOverrides( createAnchorElement: (_, __) => mockAnchor, ); - XFile file = + final XFile file = XFile.fromData(bytes, name: textFile.name, overrides: overrides); bool clicked = false; - mockAnchor.onClick.listen((event) => clicked = true); + mockAnchor.onClick.listen((html.MouseEvent event) => clicked = true); await file.saveTo('path'); diff --git a/packages/cross_file/test/x_file_io_test.dart b/packages/cross_file/test/x_file_io_test.dart index 94ac81c4ca..a8edbe530f 100644 --- a/packages/cross_file/test/x_file_io_test.dart +++ b/packages/cross_file/test/x_file_io_test.dart @@ -11,10 +11,10 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:cross_file/cross_file.dart'; -final pathPrefix = +final String pathPrefix = Directory.current.path.endsWith('test') ? './assets/' : './test/assets/'; -final path = pathPrefix + 'hello.txt'; -final String expectedStringContents = 'Hello, world!'; +final String path = pathPrefix + 'hello.txt'; +const String expectedStringContents = 'Hello, world!'; final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); final File textFile = File(path); final String textFilePath = textFile.path; @@ -39,13 +39,13 @@ void main() { }); test('saveTo(..) creates file', () async { - File removeBeforeTest = File(pathPrefix + 'newFilePath.txt'); + final File removeBeforeTest = File(pathPrefix + 'newFilePath.txt'); if (removeBeforeTest.existsSync()) { await removeBeforeTest.delete(); } await file.saveTo(pathPrefix + 'newFilePath.txt'); - File newFile = File(pathPrefix + 'newFilePath.txt'); + final File newFile = File(pathPrefix + 'newFilePath.txt'); expect(newFile.existsSync(), isTrue); expect(newFile.readAsStringSync(), 'Hello, world!'); @@ -55,7 +55,7 @@ void main() { }); group('Create with data', () { - final file = XFile.fromData(bytes); + final XFile file = XFile.fromData(bytes); test('Can be read as a string', () async { expect(await file.readAsString(), equals(expectedStringContents)); @@ -73,13 +73,13 @@ void main() { }); test('Function saveTo(..) creates file', () async { - File removeBeforeTest = File(pathPrefix + 'newFileData.txt'); + final File removeBeforeTest = File(pathPrefix + 'newFileData.txt'); if (removeBeforeTest.existsSync()) { await removeBeforeTest.delete(); } await file.saveTo(pathPrefix + 'newFileData.txt'); - File newFile = File(pathPrefix + 'newFileData.txt'); + final File newFile = File(pathPrefix + 'newFileData.txt'); expect(newFile.existsSync(), isTrue); expect(newFile.readAsStringSync(), 'Hello, world!'); From 7cb7c0a14344f0aae6a3d9c8193a649babf8129c Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 9 Mar 2021 16:53:52 -0800 Subject: [PATCH 14/14] Bump patch version and CHANGELOG. --- packages/cross_file/CHANGELOG.md | 4 ++++ packages/cross_file/pubspec.yaml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 94bf4b2932..960c870c8a 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.1+1 + +* Rehomed to `flutter/packages` repository. + ## 0.3.1 * Fix nullability of `XFileBase`'s `path` and `name` to match the diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index 66d3f46a84..5195602902 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -1,7 +1,7 @@ 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.3.1 +repository: https://github.com/flutter/packages/tree/master/packages/cross_file +version: 0.3.1+1 dependencies: flutter: @@ -14,5 +14,5 @@ dev_dependencies: pedantic: ^1.10.0 environment: - sdk: ">=2.12.0-259.9.beta <3.0.0" + sdk: ">=2.12.0 <3.0.0" flutter: ">=1.22.0"