Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6e7f572
Basic iOS implementation
spesholized Apr 27, 2022
b4e8697
Merge branch 'flutter:main' into main
spesholized Apr 28, 2022
75a1bdc
[file_selector] Basic iOS implementation
spesholized Apr 27, 2022
15f2741
Merge branch 'main' of https://github.com/spesholized/plugins
spesholized Apr 28, 2022
62c60cb
Fix formatting and update environment sdk version
spesholized Apr 28, 2022
696cb78
Basic native unit test for plugin
spesholized Apr 29, 2022
11504f4
Update packages/file_selector/file_selector_ios/example/ios/Runner.xc…
spesholized May 2, 2022
252e110
Add placeholder integration test
spesholized May 3, 2022
cb76a80
Remove unused overrides in file_selector_ios.dart, update pubspec
spesholized May 25, 2022
ecfad9a
Use pigeon, fix Obj-C prefix typo
spesholized May 26, 2022
4ecda0e
Add native unit tests
spesholized May 27, 2022
2a69911
Update tests to use pigeon, change prefix to FFS, update wildcard typ…
spesholized Aug 2, 2022
38742a3
Use non-null properties in messages.dart, use objc associated for the…
spesholized Aug 2, 2022
ec291b6
Merge branch 'main' into file_selector_ios
stuartmorgan-g Aug 19, 2022
f0bbcf1
Remove local analysis options
stuartmorgan-g Aug 19, 2022
edc2881
Resync examples and tweak UTIs
stuartmorgan-g Aug 19, 2022
e521edc
Analysis fixes
stuartmorgan-g Aug 19, 2022
eabcce5
Autoformat
stuartmorgan-g Aug 19, 2022
d39d953
Missing copyright header
stuartmorgan-g Aug 19, 2022
170c260
Supress deprecation warning on legacy codepath
stuartmorgan-g Aug 19, 2022
129a097
Review comments
stuartmorgan-g Aug 21, 2022
0cfa61c
Format
stuartmorgan-g Aug 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use non-null properties in messages.dart, use objc associated for the…
… document picker callback.
  • Loading branch information
spesholized committed Aug 2, 2022
commit 38742a3f502a2591269bc405b9530e5972d96e27
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
# platform :ios, '11.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ - (void)testPickerPresents {
plugin.documentPickerViewControllerOverride = picker;
plugin.presentingViewControllerOverride = mockPresentingVC;

[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:nil
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[]
allowMultiSelection:@NO]
completion:^(NSArray<NSString *> *paths, FlutterError *error){
}];
Expand All @@ -40,7 +40,8 @@ - (void)testReturnsPickedFiles {
UIDocumentPickerViewController *picker =
[[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[]
inMode:UIDocumentPickerModeImport];
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:nil
plugin.documentPickerViewControllerOverride = picker;
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[]
allowMultiSelection:@YES]
completion:^(NSArray<NSString *> *paths, FlutterError *error) {
NSArray *expectedPaths = @[ @"/file1.txt", @"/file2.txt" ];
Expand All @@ -52,7 +53,6 @@ - (void)testReturnsPickedFiles {
[NSURL URLWithString:@"file:///file1.txt"], [NSURL URLWithString:@"file:///file2.txt"]
]];
[self waitForExpectations:@[ completionWasCalled ] timeout:1.0];
XCTAssertNil(plugin.pendingCompletion);
}

- (void)testReturnsPickedFileLegacy {
Expand All @@ -63,7 +63,7 @@ - (void)testReturnsPickedFileLegacy {
[[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[]
inMode:UIDocumentPickerModeImport];
plugin.documentPickerViewControllerOverride = picker;
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:nil
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[]
allowMultiSelection:@NO]
completion:^(NSArray<NSString *> *paths, FlutterError *error) {
NSArray *expectedPaths = @[ @"/file1.txt" ];
Expand All @@ -75,7 +75,6 @@ - (void)testReturnsPickedFileLegacy {
[plugin documentPicker:picker didPickDocumentAtURL:[NSURL URLWithString:@"file:///file1.txt"]];
#pragma GCC diagnostic pop
[self waitForExpectations:@[ completionWasCalled ] timeout:1.0];
XCTAssertNil(plugin.pendingCompletion);
}

- (void)testCancellingPickerReturnsNil {
Expand All @@ -86,32 +85,14 @@ - (void)testCancellingPickerReturnsNil {
plugin.documentPickerViewControllerOverride = picker;

XCTestExpectation *completionWasCalled = [[XCTestExpectation alloc] init];
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:nil
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[]
allowMultiSelection:@NO]
completion:^(NSArray<NSString *> *paths, FlutterError *error) {
XCTAssertEqual(paths.count, 0);
[completionWasCalled fulfill];
}];
[plugin documentPickerWasCancelled:picker];
[self waitForExpectations:@[ completionWasCalled ] timeout:1.0];
XCTAssertNil(plugin.pendingCompletion);
}

- (void)testOpenFileSelectorWithPendingCompletionReturnsError {
FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init];
plugin.pendingCompletion = ^(NSArray<NSString *> *paths, FlutterError *error) {
};

XCTestExpectation *completionWasCalled =
[[XCTestExpectation alloc] initWithDescription:@"Completion was called"];
[plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:nil
allowMultiSelection:@NO]
completion:^(NSArray<NSString *> *paths, FlutterError *error) {
XCTAssertNotNil(error);
[completionWasCalled fulfill];
}];

[self waitForExpectations:@[ completionWasCalled ] timeout:1.0];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,32 @@
#import "FFSFileSelectorPlugin_Test.h"
#import "messages.g.h"

#import <objc/runtime.h>

@implementation FFSFileSelectorPlugin

#pragma mark - FFSFileSelectorApi

- (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config
completion:(void (^)(NSArray<NSString *> * _Nullable,
FlutterError * _Nullable))completion {
if (self.pendingCompletion) {
completion(nil, [FlutterError errorWithCode:@"error"
message:@"There is already a pending file picker request."
details:nil]);
return;
}

UIDocumentPickerViewController *documentPicker = self.documentPickerViewControllerOverride ?:
[[UIDocumentPickerViewController alloc] initWithDocumentTypes:config.utis
inMode:UIDocumentPickerModeImport];
completion:(void (^)(NSArray<NSString *> *_Nullable,
FlutterError *_Nullable))completion {
UIDocumentPickerViewController *documentPicker =
self.documentPickerViewControllerOverride
?: [[UIDocumentPickerViewController alloc]
initWithDocumentTypes:config.utis
inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
if (@available(iOS 11.0, *)) {
documentPicker.allowsMultipleSelection = config.allowMultiSelection.boolValue;
}

UIViewController *presentingVC = self.presentingViewControllerOverride ?:
UIApplication.sharedApplication.delegate.window.rootViewController;
UIViewController *presentingVC =
self.presentingViewControllerOverride
?: UIApplication.sharedApplication.delegate.window.rootViewController;
if (presentingVC) {
objc_setAssociatedObject(documentPicker, @selector(openFileSelectorWithConfig:completion:),
completion, OBJC_ASSOCIATION_COPY_NONATOMIC);
[presentingVC presentViewController:documentPicker animated:YES completion:nil];
self.pendingCompletion = completion;
} else {
completion(nil, [FlutterError errorWithCode:@"error"
message:@"Missing root view controller."
Expand All @@ -50,30 +49,35 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
#pragma mark - UIDocumentPickerDelegate

- (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentAtURL:(NSURL *)url {
didPickDocumentAtURL:(NSURL *)url {
// This method is only called in iOS < 11.0.
if (self.pendingCompletion) {
self.pendingCompletion(@[ url.path ], nil);
self.pendingCompletion = nil;
}
[self sendBackResults:@[ url.path ] error:nil forPicker:controller];
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
if (self.pendingCompletion) {
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:urls.count];
[urls enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
[paths addObject:url.path];
}];
self.pendingCompletion(paths, nil);
self.pendingCompletion = nil;
}
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:urls.count];
[urls enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
[paths addObject:url.path];
}];
[self sendBackResults:paths error:nil forPicker:controller];
}

- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
if (self.pendingCompletion) {
self.pendingCompletion(nil, nil);
self.pendingCompletion = nil;
[self sendBackResults:@[] error:nil forPicker:controller];
}

#pragma mark - Helper Methods

- (void)sendBackResults:(NSArray<NSString *> *)results
error:(FlutterError *)error
forPicker:(UIDocumentPickerViewController *)picker {
void (^completionBlock)(NSArray<NSString *> *, FlutterError *) =
objc_getAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:));
if (completionBlock) {
completionBlock(results, error);
objc_setAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:), nil,
OBJC_ASSOCIATION_ASSIGN);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@
// This header is available in the Test module. Import via "@import file_selector_ios.Test;".
@interface FFSFileSelectorPlugin() <FFSFileSelectorApi, UIDocumentPickerDelegate>

/**
* The completion block of a FFSFileSelectorApi request.
* It is saved and invoked later in a UIDocumentPickerDelegate method.
*/
@property(nonatomic) void (^_Nullable pendingCompletion)
(NSArray<NSString *> *_Nullable, FlutterError *_Nullable);
/**
* Overrides the view controller used for presenting the document picker.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ framework module file_selector_ios {
explicit module Test {
header "FFSFileSelectorPlugin_Test.h"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ NS_ASSUME_NONNULL_BEGIN
@class FFSFileSelectorConfig;

@interface FFSFileSelectorConfig : NSObject
+ (instancetype)makeWithUtis:(nullable NSArray<NSString *> *)utis
allowMultiSelection:(nullable NSNumber *)allowMultiSelection;
@property(nonatomic, strong, nullable) NSArray<NSString *> * utis;
@property(nonatomic, strong, nullable) NSNumber * allowMultiSelection;
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)makeWithUtis:(NSArray<NSString *> *)utis
allowMultiSelection:(NSNumber *)allowMultiSelection;
@property(nonatomic, strong) NSArray<NSString *> * utis;
@property(nonatomic, strong) NSNumber * allowMultiSelection;
@end

/// The codec used by FFSFileSelectorApi.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ - (NSDictionary *)toMap;
@end

@implementation FFSFileSelectorConfig
+ (instancetype)makeWithUtis:(nullable NSArray<NSString *> *)utis
allowMultiSelection:(nullable NSNumber *)allowMultiSelection {
+ (instancetype)makeWithUtis:(NSArray<NSString *> *)utis
allowMultiSelection:(NSNumber *)allowMultiSelection {
FFSFileSelectorConfig* pigeonResult = [[FFSFileSelectorConfig alloc] init];
pigeonResult.utis = utis;
pigeonResult.allowMultiSelection = allowMultiSelection;
Expand All @@ -51,7 +51,9 @@ + (instancetype)makeWithUtis:(nullable NSArray<NSString *> *)utis
+ (FFSFileSelectorConfig *)fromMap:(NSDictionary *)dict {
FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init];
pigeonResult.utis = GetNullableObject(dict, @"utis");
NSAssert(pigeonResult.utis != nil, @"");
pigeonResult.allowMultiSelection = GetNullableObject(dict, @"allowMultiSelection");
NSAssert(pigeonResult.allowMultiSelection != nil, @"");
return pigeonResult;
}
+ (nullable FFSFileSelectorConfig *)nullableFromMap:(NSDictionary *)dict { return (dict) ? [FFSFileSelectorConfig fromMap:dict] : nil; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ class FileSelectorIOS extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String>? path = (await _hostApi.openFile(FileSelectorConfig(
final List<String> path = (await _hostApi.openFile(FileSelectorConfig(
utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups),
allowMultiSelection: false)))
?.cast<String>();
return path == null ? null : XFile(path.first);
.cast<String>();
return path.isEmpty ? null : XFile(path.first);
}

@override
Expand All @@ -34,24 +34,24 @@ class FileSelectorIOS extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String>? pathList = (await _hostApi.openFile(FileSelectorConfig(
final List<String> pathList = (await _hostApi.openFile(FileSelectorConfig(
utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups),
allowMultiSelection: true)))
?.cast<String>();
return pathList?.map((String path) => XFile(path)).toList() ?? <XFile>[];
.cast<String>();
return pathList.map((String path) => XFile(path)).toList();
}

// Converts the type group list into a list of all allowed UTIs, since
// iOS doesn't support filter groups.
List<String>? _allowedUtiListFromTypeGroups(List<XTypeGroup>? typeGroups) {
List<String> _allowedUtiListFromTypeGroups(List<XTypeGroup>? typeGroups) {
if (typeGroups == null || typeGroups.isEmpty) {
return null;
return [];
}
final List<String> allowedUTIs = <String>[];
for (final XTypeGroup typeGroup in typeGroups) {
// If any group allows everything, no filtering should be done.
if (typeGroup.allowsAny) {
return null;
return [];
}
if (typeGroup.macUTIs?.isEmpty ?? true) {
throw ArgumentError('The provided type group $typeGroup should either '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import 'package:flutter/services.dart';

class FileSelectorConfig {
FileSelectorConfig({
this.utis,
this.allowMultiSelection,
required this.utis,
required this.allowMultiSelection,
});

List<String?>? utis;
bool? allowMultiSelection;
List<String?> utis;
bool allowMultiSelection;

Object encode() {
final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
Expand All @@ -29,8 +29,8 @@ class FileSelectorConfig {
static FileSelectorConfig decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return FileSelectorConfig(
utis: (pigeonMap['utis'] as List<Object?>?)?.cast<String?>(),
allowMultiSelection: pigeonMap['allowMultiSelection'] as bool?,
utis: (pigeonMap['utis'] as List<Object?>?)!.cast<String?>(),
allowMultiSelection: pigeonMap['allowMultiSelection']! as bool,
);
}
}
Expand Down Expand Up @@ -70,7 +70,7 @@ class FileSelectorApi {

static const MessageCodec<Object?> codec = _FileSelectorApiCodec();

Future<List<String?>?> openFile(FileSelectorConfig arg_config) async {
Future<List<String?>> openFile(FileSelectorConfig arg_config) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.FileSelectorApi.openFile', codec, binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
Expand All @@ -87,8 +87,13 @@ class FileSelectorApi {
message: error['message'] as String?,
details: error['details'],
);
} else if (replyMap['result'] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyMap['result'] as List<Object?>?)?.cast<String?>();
return (replyMap['result'] as List<Object?>?)!.cast<String?>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import 'package:pigeon/pigeon.dart';
class FileSelectorConfig {
FileSelectorConfig(
{this.utis = const <String?>[], this.allowMultiSelection = false});
List<String?>? utis;
bool? allowMultiSelection;
List<String?> utis;
bool allowMultiSelection;
}

@HostApi(dartHostTestHandler: 'TestFileSelectorApi')
abstract class FileSelectorApi {
@async
@ObjCSelector('openFileSelectorWithConfig:')
List<String>? openFile(FileSelectorConfig config);
List<String> openFile(FileSelectorConfig config);
}
2 changes: 1 addition & 1 deletion packages/file_selector/file_selector_ios/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: file_selector_ios
description: iOS implementation of the file_selector plugin.
repository: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_ios
repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
version: 0.5.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class MockTestFileSelectorApi extends _i1.Mock
}

@override
_i3.Future<List<String?>?> openFile(_i4.FileSelectorConfig? config) =>
_i3.Future<List<String?>> openFile(_i4.FileSelectorConfig? config) =>
(super.noSuchMethod(Invocation.method(#openFile, [config]),
returnValue: _i3.Future<List<String?>?>.value())
as _i3.Future<List<String?>?>);
returnValue: _i3.Future<List<String?>>.value(<String?>[]))
as _i3.Future<List<String?>>);
}
4 changes: 2 additions & 2 deletions packages/file_selector/file_selector_ios/test/test_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec {
abstract class TestFileSelectorApi {
static const MessageCodec<Object?> codec = _TestFileSelectorApiCodec();

Future<List<String?>?> openFile(FileSelectorConfig config);
Future<List<String?>> openFile(FileSelectorConfig config);
static void setup(TestFileSelectorApi? api,
{BinaryMessenger? binaryMessenger}) {
{
Expand All @@ -61,7 +61,7 @@ abstract class TestFileSelectorApi {
(args[0] as FileSelectorConfig?);
assert(arg_config != null,
'Argument for dev.flutter.pigeon.FileSelectorApi.openFile was null, expected non-null FileSelectorConfig.');
final List<String?>? output = await api.openFile(arg_config!);
final List<String?> output = await api.openFile(arg_config!);
return <Object?, Object?>{'result': output};
});
}
Expand Down