diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index fe01ffec47b6..64ac5959a7c0 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.0 + +Migrate to null safety. + ## 0.7.0+2 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. diff --git a/packages/file_selector/file_selector/example/lib/get_directory_page.dart b/packages/file_selector/file_selector/example/lib/get_directory_page.dart index 6463fb532957..cf4cde9fa9a8 100644 --- a/packages/file_selector/file_selector/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector/example/lib/get_directory_page.dart @@ -5,9 +5,13 @@ import 'package:flutter/material.dart'; class GetDirectoryPage extends StatelessWidget { void _getDirectoryPath(BuildContext context) async { final String confirmButtonText = 'Choose'; - final String directoryPath = await getDirectoryPath( + final String? directoryPath = await getDirectoryPath( confirmButtonText: confirmButtonText, ); + if (directoryPath == null) { + // Operation was canceled by the user. + return; + } await showDialog( context: context, builder: (context) => TextDisplay(directoryPath), diff --git a/packages/file_selector/file_selector/example/lib/open_image_page.dart b/packages/file_selector/file_selector/example/lib/open_image_page.dart index 593a1d60aed8..986bfe712893 100644 --- a/packages/file_selector/file_selector/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_image_page.dart @@ -11,6 +11,10 @@ class OpenImagePage extends StatelessWidget { extensions: ['jpg', 'png'], ); final List files = await openFiles(acceptedTypeGroups: [typeGroup]); + if (files.isEmpty) { + // Operation was canceled by the user. + return; + } final XFile file = files[0]; final String fileName = file.name; final String filePath = file.path; diff --git a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart index 58b59cd91b03..c6f73f5aed12 100644 --- a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart @@ -18,6 +18,10 @@ class OpenMultipleImagesPage extends StatelessWidget { jpgsTypeGroup, pngTypeGroup, ]); + if (files.isEmpty) { + // Operation was canceled by the user. + return; + } await showDialog( context: context, builder: (context) => MultipleImagesDisplay(files), diff --git a/packages/file_selector/file_selector/example/lib/open_text_page.dart b/packages/file_selector/file_selector/example/lib/open_text_page.dart index 299d0e2dc21a..74d79dd72b19 100644 --- a/packages/file_selector/file_selector/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_text_page.dart @@ -8,7 +8,11 @@ class OpenTextPage extends StatelessWidget { label: 'text', extensions: ['txt', 'json'], ); - final XFile file = await openFile(acceptedTypeGroups: [typeGroup]); + final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); + if (file == null) { + // Operation was canceled by the user. + return; + } final String fileName = file.name; final String fileContent = await file.readAsString(); diff --git a/packages/file_selector/file_selector/example/lib/save_text_page.dart b/packages/file_selector/file_selector/example/lib/save_text_page.dart index 47408662ecee..82046c35128a 100644 --- a/packages/file_selector/file_selector/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/save_text_page.dart @@ -8,7 +8,11 @@ class SaveTextPage extends StatelessWidget { final TextEditingController _contentController = TextEditingController(); void _saveFile() async { - final String path = await getSavePath(); + String? path = await getSavePath(); + if (path == null) { + // Operation was canceled by the user. + return; + } final String text = _contentController.text; final String fileName = _nameController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index 3af2a67e9e93..580237cad5ac 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -1,24 +1,12 @@ name: example description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0-259.9.beta <3.0.0" dependencies: flutter: @@ -32,52 +20,9 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - dev_dependencies: flutter_test: sdk: flutter -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index 080eac4460ac..cdb2bf9c726d 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -10,10 +10,10 @@ export 'package:file_selector_platform_interface/file_selector_platform_interfac show XFile, XTypeGroup; /// Open file dialog for loading files and return a file path -Future openFile({ - List acceptedTypeGroups, - String initialDirectory, - String confirmButtonText, +Future openFile({ + List acceptedTypeGroups = const [], + String? initialDirectory, + String? confirmButtonText, }) { return FileSelectorPlatform.instance.openFile( acceptedTypeGroups: acceptedTypeGroups, @@ -23,9 +23,9 @@ Future openFile({ /// Open file dialog for loading files and return a list of file paths Future> openFiles({ - List acceptedTypeGroups, - String initialDirectory, - String confirmButtonText, + List acceptedTypeGroups = const [], + String? initialDirectory, + String? confirmButtonText, }) { return FileSelectorPlatform.instance.openFiles( acceptedTypeGroups: acceptedTypeGroups, @@ -34,11 +34,11 @@ Future> openFiles({ } /// Saves File to user's file system -Future getSavePath({ - List acceptedTypeGroups, - String initialDirectory, - String suggestedName, - String confirmButtonText, +Future getSavePath({ + List acceptedTypeGroups = const [], + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, }) async { return FileSelectorPlatform.instance.getSavePath( acceptedTypeGroups: acceptedTypeGroups, @@ -48,9 +48,9 @@ Future getSavePath({ } /// Gets a directory path from a user's file system -Future getDirectoryPath({ - String initialDirectory, - String confirmButtonText, +Future getDirectoryPath({ + String? initialDirectory, + String? confirmButtonText, }) async { return FileSelectorPlatform.instance.getDirectoryPath( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText); diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index a55b7f4e06e7..34b459cca720 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -1,21 +1,20 @@ name: file_selector description: Flutter plugin for opening and saving files. homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector -version: 0.7.0+2 +version: 0.8.0 dependencies: flutter: sdk: flutter - file_selector_platform_interface: ^1.0.0 + file_selector_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter - test: ^1.3.0 - mockito: ^4.1.1 - plugin_platform_interface: ^1.0.0 - pedantic: ^1.8.0 + test: ^1.16.3 + plugin_platform_interface: ">=1.0.0 <3.0.0" + pedantic: ^1.10.0 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5" + sdk: ">=2.12.0-259.9.beta <3.0.0" + flutter: ">=1.20.0" diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index 15756cc2b622..b16f234bb3b8 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -3,13 +3,13 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:file_selector/file_selector.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:test/fake.dart'; void main() { - MockFileSelector mock; + late FakeFileSelector fakePlatformImplementation; final initialDirectory = '/home/flutteruser'; final confirmButtonText = 'Use this profile picture'; final suggestedName = 'suggested_name'; @@ -25,19 +25,20 @@ void main() { ]; setUp(() { - mock = MockFileSelector(); - FileSelectorPlatform.instance = mock; + fakePlatformImplementation = FakeFileSelector(); + FileSelectorPlatform.instance = fakePlatformImplementation; }); group('openFile', () { final expectedFile = XFile('path'); test('works', () async { - when(mock.openFile( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - acceptedTypeGroups: acceptedTypeGroups, - )).thenAnswer((_) => Future.value(expectedFile)); + fakePlatformImplementation + ..setExpectations( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + acceptedTypeGroups: acceptedTypeGroups) + ..setFileResponse([expectedFile]); final file = await openFile( initialDirectory: initialDirectory, @@ -49,7 +50,7 @@ void main() { }); test('works with no arguments', () async { - when(mock.openFile()).thenAnswer((_) => Future.value(expectedFile)); + fakePlatformImplementation.setFileResponse([expectedFile]); final file = await openFile(); @@ -57,24 +58,27 @@ void main() { }); test('sets the initial directory', () async { - when(mock.openFile(initialDirectory: initialDirectory)) - .thenAnswer((_) => Future.value(expectedFile)); + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setFileResponse([expectedFile]); final file = await openFile(initialDirectory: initialDirectory); expect(file, expectedFile); }); test('sets the button confirmation label', () async { - when(mock.openFile(confirmButtonText: confirmButtonText)) - .thenAnswer((_) => Future.value(expectedFile)); + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setFileResponse([expectedFile]); final file = await openFile(confirmButtonText: confirmButtonText); expect(file, expectedFile); }); test('sets the accepted type groups', () async { - when(mock.openFile(acceptedTypeGroups: acceptedTypeGroups)) - .thenAnswer((_) => Future.value(expectedFile)); + fakePlatformImplementation + ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) + ..setFileResponse([expectedFile]); final file = await openFile(acceptedTypeGroups: acceptedTypeGroups); expect(file, expectedFile); @@ -85,11 +89,12 @@ void main() { final expectedFiles = [XFile('path')]; test('works', () async { - when(mock.openFiles( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - acceptedTypeGroups: acceptedTypeGroups, - )).thenAnswer((_) => Future.value(expectedFiles)); + fakePlatformImplementation + ..setExpectations( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + acceptedTypeGroups: acceptedTypeGroups) + ..setFileResponse(expectedFiles); final file = await openFiles( initialDirectory: initialDirectory, @@ -101,7 +106,7 @@ void main() { }); test('works with no arguments', () async { - when(mock.openFiles()).thenAnswer((_) => Future.value(expectedFiles)); + fakePlatformImplementation.setFileResponse(expectedFiles); final files = await openFiles(); @@ -109,24 +114,27 @@ void main() { }); test('sets the initial directory', () async { - when(mock.openFiles(initialDirectory: initialDirectory)) - .thenAnswer((_) => Future.value(expectedFiles)); + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setFileResponse(expectedFiles); final files = await openFiles(initialDirectory: initialDirectory); expect(files, expectedFiles); }); test('sets the button confirmation label', () async { - when(mock.openFiles(confirmButtonText: confirmButtonText)) - .thenAnswer((_) => Future.value(expectedFiles)); + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setFileResponse(expectedFiles); final files = await openFiles(confirmButtonText: confirmButtonText); expect(files, expectedFiles); }); test('sets the accepted type groups', () async { - when(mock.openFiles(acceptedTypeGroups: acceptedTypeGroups)) - .thenAnswer((_) => Future.value(expectedFiles)); + fakePlatformImplementation + ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) + ..setFileResponse(expectedFiles); final files = await openFiles(acceptedTypeGroups: acceptedTypeGroups); expect(files, expectedFiles); @@ -137,12 +145,13 @@ void main() { final expectedSavePath = '/example/path'; test('works', () async { - when(mock.getSavePath( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - acceptedTypeGroups: acceptedTypeGroups, - suggestedName: suggestedName, - )).thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation + ..setExpectations( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + acceptedTypeGroups: acceptedTypeGroups, + suggestedName: suggestedName) + ..setPathResponse(expectedSavePath); final savePath = await getSavePath( initialDirectory: initialDirectory, @@ -155,32 +164,34 @@ void main() { }); test('works with no arguments', () async { - when(mock.getSavePath()) - .thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation.setPathResponse(expectedSavePath); final savePath = await getSavePath(); expect(savePath, expectedSavePath); }); test('sets the initial directory', () async { - when(mock.getSavePath(initialDirectory: initialDirectory)) - .thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setPathResponse(expectedSavePath); final savePath = await getSavePath(initialDirectory: initialDirectory); expect(savePath, expectedSavePath); }); test('sets the button confirmation label', () async { - when(mock.getSavePath(confirmButtonText: confirmButtonText)) - .thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setPathResponse(expectedSavePath); final savePath = await getSavePath(confirmButtonText: confirmButtonText); expect(savePath, expectedSavePath); }); test('sets the accepted type groups', () async { - when(mock.getSavePath(acceptedTypeGroups: acceptedTypeGroups)) - .thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation + ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) + ..setPathResponse(expectedSavePath); final savePath = await getSavePath(acceptedTypeGroups: acceptedTypeGroups); @@ -188,8 +199,9 @@ void main() { }); test('sets the suggested name', () async { - when(mock.getSavePath(suggestedName: suggestedName)) - .thenAnswer((_) => Future.value(expectedSavePath)); + fakePlatformImplementation + ..setExpectations(suggestedName: suggestedName) + ..setPathResponse(expectedSavePath); final savePath = await getSavePath(suggestedName: suggestedName); expect(savePath, expectedSavePath); @@ -200,10 +212,11 @@ void main() { final expectedDirectoryPath = '/example/path'; test('works', () async { - when(mock.getDirectoryPath( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - )).thenAnswer((_) => Future.value(expectedDirectoryPath)); + fakePlatformImplementation + ..setExpectations( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText) + ..setPathResponse(expectedDirectoryPath); final directoryPath = await getDirectoryPath( initialDirectory: initialDirectory, @@ -214,16 +227,16 @@ void main() { }); test('works with no arguments', () async { - when(mock.getDirectoryPath()) - .thenAnswer((_) => Future.value(expectedDirectoryPath)); + fakePlatformImplementation.setPathResponse(expectedDirectoryPath); final directoryPath = await getDirectoryPath(); expect(directoryPath, expectedDirectoryPath); }); test('sets the initial directory', () async { - when(mock.getDirectoryPath(initialDirectory: initialDirectory)) - .thenAnswer((_) => Future.value(expectedDirectoryPath)); + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setPathResponse(expectedDirectoryPath); final directoryPath = await getDirectoryPath(initialDirectory: initialDirectory); @@ -231,8 +244,9 @@ void main() { }); test('sets the button confirmation label', () async { - when(mock.getDirectoryPath(confirmButtonText: confirmButtonText)) - .thenAnswer((_) => Future.value(expectedDirectoryPath)); + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setPathResponse(expectedDirectoryPath); final directoryPath = await getDirectoryPath(confirmButtonText: confirmButtonText); @@ -241,6 +255,83 @@ void main() { }); } -class MockFileSelector extends Mock +class FakeFileSelector extends Fake with MockPlatformInterfaceMixin - implements FileSelectorPlatform {} + implements FileSelectorPlatform { + // Expectations. + List? acceptedTypeGroups = const []; + String? initialDirectory; + String? confirmButtonText; + String? suggestedName; + // Return values. + List? files; + String? path; + + void setExpectations({ + List acceptedTypeGroups = const [], + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, + }) { + this.acceptedTypeGroups = acceptedTypeGroups; + this.initialDirectory = initialDirectory; + this.suggestedName = suggestedName; + this.confirmButtonText = confirmButtonText; + } + + void setFileResponse(List files) { + this.files = files; + } + + void setPathResponse(String path) { + this.path = path; + } + + @override + Future openFile({ + List? acceptedTypeGroups, + String? initialDirectory, + String? confirmButtonText, + }) async { + expect(acceptedTypeGroups, this.acceptedTypeGroups); + expect(initialDirectory, this.initialDirectory); + expect(suggestedName, this.suggestedName); + return files?[0]; + } + + @override + Future> openFiles({ + List? acceptedTypeGroups, + String? initialDirectory, + String? confirmButtonText, + }) async { + expect(acceptedTypeGroups, this.acceptedTypeGroups); + expect(initialDirectory, this.initialDirectory); + expect(suggestedName, this.suggestedName); + return files!; + } + + @override + Future getSavePath({ + List? acceptedTypeGroups, + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, + }) async { + expect(acceptedTypeGroups, this.acceptedTypeGroups); + expect(initialDirectory, this.initialDirectory); + expect(suggestedName, this.suggestedName); + expect(confirmButtonText, this.confirmButtonText); + return path; + } + + @override + Future getDirectoryPath({ + String? initialDirectory, + String? confirmButtonText, + }) async { + expect(initialDirectory, this.initialDirectory); + expect(confirmButtonText, this.confirmButtonText); + return path; + } +}