-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[file_selector] Basic iOS implementation #5448
Conversation
cbc69f4
to
1166325
Compare
1166325
to
696cb78
Compare
packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj
Outdated
Show resolved
Hide resolved
…odeproj/project.pbxproj Co-authored-by: Jenn Magder <[email protected]>
Friendly ping! @jmagman @stuartmorgan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the submission! See comments inline.
|
||
- (void)testPlugin { | ||
FTLFileSelectorPlugin *plugin = [[FTLFileSelectorPlugin alloc] init]; | ||
XCTAssertNotNil(plugin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests that look like this are the result of flutter/flutter#83357
New code needs actual unit tests. See local_auth
or camera
for real examples. (Maybe also file_selector_macos
, for a more relevant domain but the wrong language.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/file_selector/file_selector_ios/lib/file_selector_ios.dart
Outdated
Show resolved
Hide resolved
if ([@"openFile" isEqualToString:call.method]) { | ||
[self openPickerWithCall:call result:result allowMultiSelection:NO]; | ||
} else if ([@"openFiles" isEqualToString:call.method]) { | ||
[self openPickerWithCall:call result:result allowMultiSelection:YES]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The platform channel layer doesn't need to directly mirror the platform interface. While you'll see that in many of our plugins, it's almost entirely for historical reasons, and it's not something we want to do in new plugins when it doesn't actually make sense. It's pretty clear here that a single method call with a boolean argument would be a much better representation; you can do that mapping in the Dart code (which is easier to test/debug in general) instead of native code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
for (final XTypeGroup typeGroup in typeGroups) { | ||
// If any group allows everything, no filtering should be done. | ||
if ((typeGroup.extensions?.isEmpty ?? true) && | ||
(typeGroup.macUTIs?.isEmpty ?? true) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This turns out to have been poor naming :( Filed flutter/flutter#103743
(Doesn't need to block this PR, just leaving here for future reference.)
|
||
// Converts the type group list into a flat list of all allowed types, since | ||
// iOS doesn't support filter groups. | ||
Map<String, List<String>>? _allowedTypeListFromTypeGroups( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you're only actually using UTI, so you can just make this a List<String>
of UTIs, and remove a level of nesting at both ends of the channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
String? suggestedName, | ||
String? confirmButtonText, | ||
}) async { | ||
return _channel.invokeMethod<String>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why call a method you know doesn't exist? You can just throw UnimplementedError
. Or even just not override getSavePath
in the first place, since that's what the superclass does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Removed unused overrides.
String? initialDirectory, | ||
String? confirmButtonText, | ||
}) async { | ||
return _channel.invokeMethod<String>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
environment: | ||
sdk: ">=2.14.4 <3.0.0" | ||
flutter: ">=2.0.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a new plugin, use 2.8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
pluginClass: FTLFileSelectorPlugin | ||
|
||
dependencies: | ||
cross_file: ^0.3.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need this dependency? f_s_p_i should be handling that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
dev_dependencies: | ||
flutter_lints: ^1.0.0 | ||
flutter_test: | ||
sdk: flutter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the missing newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly this looks good at a high level; specific comments inline. I didn't really review the Dart unit tests since they need to be rewritten.
|
||
import 'src/messages.g.dart'; | ||
|
||
const MethodChannel _channel = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cruft, and can be removed along with the method to access it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
final FileSelectorIOS plugin = FileSelectorIOS(); | ||
|
||
final List<MethodCall> log = <MethodCall>[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an example of what writing new tests Dart tests for a Pigeon implementation should probably look like, see https://github.com/stuartmorgan/plugins/blob/file-selector-windows-pigeon/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart (which is from a WIP PR to convert Windows file_selector to Pigeon; our currently-landed examples are mostly straight conversions of tests from this style to minimize test changes when re-implementing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I also removed some of the tests (that were copied from macOS version) because only openFile and openFiles are implemented at the moment.
import 'package:integration_test/integration_test.dart'; | ||
|
||
void main() { | ||
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In cases like this where there's no integration test we can run from the Dart side, instead of stub files you should add an entry to script/configs/exclude_integration_ios.yaml
with an explanatory comment (which for desktop implementations of file_selector
was # Can't use Flutter integration tests due to native modal UI.
).
That way our CI output will report (correctly) that it was excluded from tests, rather that reporting a misleading output that all tests passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
objcHeaderOut: 'ios/Classes/messages.g.h', | ||
objcSourceOut: 'ios/Classes/messages.g.m', | ||
objcOptions: ObjcOptions( | ||
prefix: 'FLT', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using per-plugin prefixes (F for Flutter plus two letters based on the plugin name) going forward, to reduce the possibility of inter-plugin collisions, so this should be FFS. Same for the plugin files/classes that are named manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
)) | ||
class FileSelectorConfig { | ||
FileSelectorConfig(this.utis, this.allowMultiSelection); | ||
List<String?>? utis; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Dart we prefer to make collections non-nullable, so that we don't have two ways of representing the same thing (null and empty), and defaulting to empty collections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups), | ||
allowMultiSelection: false))) | ||
?.cast<String>(); | ||
return path == null ? null : XFile(path.first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this is why we have a general policy against nullable collections: this line only correctly handles one form of "empty" (null), not the other, making it very fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
// 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<String>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// iOS doesn't support filter groups. | ||
List<String>? _allowedUtiListFromTypeGroups(List<XTypeGroup>? typeGroups) { | ||
if (typeGroups == null || typeGroups.isEmpty) { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then these should return empty collections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@spesholized Friendly ping. Have you gotten a chance to look at Stuart's comment? |
Sorry for the delay, I've been busy with something else in the past couple of weeks but I do want to get this change in soon. I should have some time to review and address the comments in a few days. |
… document picker callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I synced in some recent changes from master, and fixed a few minor issues; with that, LTGM!
@jmagman This is ready for a second review.
packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'm adopting this since the author is out for the moment.)
packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m
Outdated
Show resolved
Hide resolved
packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once the formatter is happy
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatter didn't like this:
These files are not formatted correctly (see diff below):
file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
To fix run "pub global activate flutter_plugin_tools && pub global run flutter_plugin_tools format" or copy-paste this command into your terminal:
patch -p1 <<DONE
diff --git a/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h b/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
index 122724e46..d79d3642b 100644
--- a/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
+++ b/packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h
@@ -3,5 +3,4 @@
// found in the LICENSE file.
#import <Foundation/Foundation.h>
-
#import <file_selector_ios/FFSFileSelectorPlugin.h>
DONE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, clang-format must assume that any framework-style #import
should be a single block. Oh well.
Initial iOS implementation.
Fixes flutter/flutter#102777
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).