Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[file_selector] Basic iOS implementation #5448

Merged
merged 22 commits into from
Aug 22, 2022

Conversation

spesholized
Copy link
Contributor

Initial iOS implementation.

Fixes flutter/flutter#102777

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/plugins repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • I updated CHANGELOG.md to add a description of the change, following repository CHANGELOG style.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

@spesholized spesholized marked this pull request as ready for review May 3, 2022 18:30
@stuartmorgan stuartmorgan self-requested a review May 4, 2022 01:24
@spesholized spesholized requested a review from jmagman May 9, 2022 19:06
@spesholized
Copy link
Contributor Author

Friendly ping! @jmagman @stuartmorgan

Copy link
Contributor

@stuartmorgan stuartmorgan left a 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);
Copy link
Contributor

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.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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];
Copy link
Contributor

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.

Copy link
Contributor Author

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) &&
Copy link
Contributor

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(
Copy link
Contributor

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.

Copy link
Contributor Author

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>(
Copy link
Contributor

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.

Copy link
Contributor Author

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>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

Copy link
Contributor Author

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"
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@spesholized spesholized requested a review from stuartmorgan May 27, 2022 00:32
Copy link
Contributor

@stuartmorgan stuartmorgan left a 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 =
Copy link
Contributor

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.

Copy link
Contributor Author

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>[];
Copy link
Contributor

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).

Copy link
Contributor Author

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();
Copy link
Contributor

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.

Copy link
Contributor Author

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',
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List<String>

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@cyanglaz
Copy link
Contributor

@spesholized Friendly ping. Have you gotten a chance to look at Stuart's comment?

@spesholized
Copy link
Contributor Author

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.

Copy link
Contributor

@stuartmorgan stuartmorgan left a 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.

Copy link
Contributor

@stuartmorgan stuartmorgan left a 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.)

Copy link
Member

@jmagman jmagman left a 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


Copy link
Member

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

Copy link
Contributor

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.

@stuartmorgan stuartmorgan added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 22, 2022
@auto-submit auto-submit bot merged commit 8f3749c into flutter:main Aug 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 23, 2022
@jmagman jmagman mentioned this pull request Aug 23, 2022
11 tasks
moisefeelin pushed a commit to feelinproject/plugins that referenced this pull request Aug 26, 2022
mauricioluz pushed a commit to mauricioluz/plugins that referenced this pull request Jan 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: file_selector platform-ios
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[file_selector] Add iOS support
4 participants