Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[local_auth] Migrate iOS to Pigeon #3974

Merged
merged 15 commits into from
May 19, 2023

Conversation

stuartmorgan
Copy link
Contributor

Updates the platform communication to use Pigeon. This includes some changes to the API boundary:

  • Collected authentication options into an options object; this avoids having a lot of positional boolean arguments (since Pigeon doesn't currently support named arguments).
  • Collected strings into a strings object, since having them as individual parameters would have been extremely messy.
  • Changes the authenticate return from a bool+exceptions to an enum that encompasses all of the explicitly known failure modes. To avoid a breaking change for clients, the Dart code creates PlatformExceptions that match the old ones, but this will make it much easier to someday make a (cross-platform) breaking change to replace them with better errors per the best practices we have documented on the wiki. Using an enum rather than throwing errors avoids the need to do string matching when we want to eventually translate them into something other than PlatformException.

The Pigeon file, Dart implementation, and rewritten tests are all based very heavily on the recent Android migration: #3748

I noted several bugs that I noticed in the existing implementation during the migration. I intentionally didn't fix them so this isn't changing behavior at the same time that it's changing so much of the code and tests, but I marked them with TODO comments.

Fixes flutter/flutter#117912

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/packages repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the package 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.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@stuartmorgan stuartmorgan changed the title Local auth ios pigeon [local_auth] Migrate iOS to Pigeon May 12, 2023
Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

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

a few questions but overall makes sense

packages/local_auth/local_auth_ios/lib/local_auth_ios.dart Outdated Show resolved Hide resolved
packages/local_auth/local_auth_ios/pigeons/messages.dart Outdated Show resolved Hide resolved
completion([FLAAuthResultDetails makeWithValue:result
errorMessage:authError.localizedDescription
errorDetails:authError.domain],
nil);
Copy link
Contributor

Choose a reason for hiding this comment

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

is it more natural to just complete with an NSError here? Or is there any issue for pigeon to handle error case properly?

completion(result, [NSError errorWith...])

Copy link
Contributor

Choose a reason for hiding this comment

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

oops FlutterError rather.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See comments below.


class AuthResultDetails {
AuthResultDetails(
{required this.value, this.errorMessage, this.errorDetails});
Copy link
Contributor

Choose a reason for hiding this comment

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

is it standard/recommended to wrap error messages/details in a pigeon method call's return type? this seems a bit tedious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what you mean by "standard". In practice, it's standard for our plugins to just throw arbitrary PlatformExceptions from native code and let those escape all the way out to plugin clients do try to do things like match arbitrary strings to figure out what the conceptual errors are. But it shouldn't be.

This design is a logical extension of our recommendation in https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#platform-exception-handling. See the third bullet point in the PR description.

details is a temporary field that allows making this structural change in the language boundary API separately from the eventual breaking change of reworking the errors/exceptions we return from the plugin.

Copy link
Contributor

Choose a reason for hiding this comment

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

I could be missing something, but the signature of completion takes an error, so I assume the "standard" approach is to pass in the error when error occurs:

completion:(void (^)(FLAAuthResultDetails *_Nullable,
                                          FlutterError *_Nullable))completion

About error translation, can we catch the error in local_auth_ios.dart and translate it, before it escapes into client logic?

In the current approach, however, plugin authors will have to manually include error type/message/details in their custom types, rather than directly using the FlutterError type. This feels a bit tedious to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could be missing something, but the signature of completion takes an error, so I assume the "standard" approach is to pass in the error when error occurs:

completion:(void (^)(FLAAuthResultDetails *_Nullable,
                                          FlutterError *_Nullable))completion

The signature takes an error because that gives Pigeon feature parity with the underlying channel API, such that someone can convert existing implementations to platform channels with minimal changes if they so desire. The existence of the possibility to do something in an API doesn't mean it's always the right way to do everything.

About error translation, can we catch the error in local_auth_ios.dart and translate it, before it escapes into client logic?

We could, yes. I explained why I don't want to do that in the part of the PR description that I referred to in my previous comment. Can you explain what about the argument there you disagree with?

In the current approach, however, plugin authors will have to manually include error type/message/details in their custom types, rather than directly using the FlutterError type.

I'm not sure who "plugin authors" is. This is one plugin, which we own. I'm not forcing the entire ecosystem to use this approach if they don't want to.

Also, I've already explained that that's not the case. details is only there because I don't want this to be a breaking change if clients are relying on exact platform exception details (due to us not having structured errors). That may or may not be true of message; we'll need to evaluate whether it's useful to preserve as-is in the combined error when the plugin is reworked. I think you are over-indexing on this one particular case that's not representative of the general design. Compare to local_auth_android, for instance, where we just return an enum because there are no messages or details provided by the OS.

If you really want me to, I can revert that part and go back to throwing PlatformExceptions directly from native code like we were before, and leave a TODO to recreate this work when we standardize errors. I was trying to do more of that work now and keep iOS more consistent with Android, but it's not required.

This feels a bit tedious to do.

Is it more tedious than defining string constants in both Dart and ObjC for every type that we want to catch and convert, manually verifying that the strings match exactly, and manually verifying that everything we throw is indeed caught in a different file written in a different language?

Copy link
Contributor

@hellohuanlin hellohuanlin May 18, 2023

Choose a reason for hiding this comment

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

The signature takes an error because that gives Pigeon feature parity with the underlying channel API, such that someone can convert existing implementations to platform channels with minimal changes if they so desire.

I did not know that this error argument is intended for parity with platform channels. Just from the signature alone, I'd expect it to be used whenever an error happens.

We could, yes. I explained why I don't want to do that in the part of the PR description that I referred to in my previous comment. Can you explain what about the argument there you disagree with?

I probably misinterpreted the PR description: it says throwing error, rather than passing the error to completion block. Though you are probably referring to passing the error to completion, which will trigger dart's exception to be thrown.

I'm not sure who "plugin authors" is. This is one plugin, which we own. I'm not forcing the entire ecosystem to use this approach if they don't want to.

I think whatever we do in 1p plugins likely will become something that other plugin authors consider as the "golden standard", so just wanna make sure it's the best practice.

Is it more tedious than defining string constants in both Dart and ObjC for every type that we want to catch and convert, manually verifying that the strings match exactly, and manually verifying that everything we throw is indeed caught in a different file written in a different language?

Good point. This approach is definitely less error-prone than using FlutterError/PlatformException. I think extra fields in custom types is worth the benefit then. Are you seeing a world where we don't use FlutterError/PlatformException at all in 1P plugins with pigeon? (i.e. the completion's error argument is never used)

Copy link
Contributor Author

@stuartmorgan stuartmorgan May 19, 2023

Choose a reason for hiding this comment

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

Are you seeing a world where we don't use FlutterError/PlatformException at all in 1P plugins with pigeon? (i.e. the completion's error argument is never used)

I don't think so at the moment; we'll need to see how things go as we apply the current guidance to more plugins. What it currently says is:

This means that in general, clients of a plugin should not be expected to see raw PlatformExceptions created from error responses in native code. (This is not a strict rule; failure cases that are so obscure that clients would be unlikely to actually have specific handlers for them don't necessarily need to be converted to a common exception type.)

An example of a case where I think this applies would be url_launcher:

  • On iOS, it can throw if you pass an invalid URL according to NSURL's definition; this can only happen if you either a) use the String-based API that we explicitly say in dangerous and you should almost never use and do so incorrectly, or b) manage to need a URL that is a valid Dart Uri but not a valid NSURL, which should be exceedingly rare (this is why the String-based API still exists, but I know of only one such case currently and it's very niche).
  • On Android it can throw if you have no current Activity when you try to launch a URL, which would be a pretty weird thing to have happen in actual usage.

I think these both fall under the "unlikely to actually have specific handlers", and there's no cross-platform conceptual thing to map them to because they are so specific, so they seem like valid cases for native errors.

I do expect it to be pretty rare though, as we move toward following the new guidance.

final String? errorMessage;

/// System-provided error details, if any.
// TODO(stuartmorgan): Remove this when standardizing errors plugin-wide in
Copy link
Contributor

Choose a reason for hiding this comment

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

is it because other platforms don't use errorDetails?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's because I don't expect there to be anything in this field for these use cases that is actually important enough that we would preserve it for structured errors.

Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

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

This LGTM! Don't have anything blocking. Thanks for the explanation.

@stuartmorgan stuartmorgan added the autosubmit Merge PR when tree becomes green via auto submit App label May 19, 2023
@auto-submit auto-submit bot merged commit 85e7e10 into flutter:main May 19, 2023
68 checks passed
@stuartmorgan stuartmorgan deleted the local-auth-ios-pigeon branch May 19, 2023 20:49
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 22, 2023
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request May 22, 2023
flutter/packages@1e214d7...83959fb

2023-05-22 JeroenWeener@users.noreply.github.com [in_app_purchases] Fix mismatching method signature strings (flutter/packages#4040)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 engine-flutter-autoroll@skia.org Roll Flutter from 077d644 to ab57304 (18 revisions) (flutter/packages#4051)
2023-05-19 engine-flutter-autoroll@skia.org Roll Flutter from 5ae6438 to 077d644 (23 revisions) (flutter/packages#4043)
2023-05-19 stuartmorgan@google.com [local_auth] Migrate iOS to Pigeon (flutter/packages#3974)
2023-05-19 hamadyalghanim@gmail.com [go_router] fix context extension for replaceNamed (flutter/packages#3927)
2023-05-19 JeroenWeener@users.noreply.github.com [image_picker] Fix crash due to `SecurityException` (flutter/packages#4004)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com,rmistry@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
sybrands-place pushed a commit to sybrands-place/packages that referenced this pull request May 23, 2023
* main: (104 commits)
  [various] Remove unnecessary null checks (flutter#4060)
  [ci] Add a legacy Android build-all test (flutter#4005)
  Roll Flutter from ab573048e74d to 343718945bcb (5 revisions) (flutter#4062)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  [in_app_purchases] Fix mismatching method signature strings (flutter#4040)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  [go_router] Nested stateful navigation with ShellRoute (flutter#2650)
  Roll Flutter from 077d644bbc5f to ab573048e74d (18 revisions) (flutter#4051)
  Roll Flutter from 5ae64381578c to 077d644bbc5f (23 revisions) (flutter#4043)
  [local_auth] Migrate iOS to Pigeon (flutter#3974)
  [go_router] fix context extension for replaceNamed (flutter#3927)
  [image_picker] Fix crash due to `SecurityException` (flutter#4004)
  Roll Flutter from d0d1feb6283f to 5ae64381578c (42 revisions) (flutter#4038)
  [ci] Lower iOS LUCI timeouts (flutter#4035)
  [ci] Increase Android sharding (flutter#4029)
  [flutter_plugin_android_lifecycle] Fix lints (flutter#4030)
  [rfw] Fix a typo in the API documentation (flutter#4023)
  ...
CaseyHillers pushed a commit to CaseyHillers/flutter that referenced this pull request May 24, 2023
flutter/packages@1e214d7...83959fb

2023-05-22 JeroenWeener@users.noreply.github.com [in_app_purchases] Fix mismatching method signature strings (flutter/packages#4040)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 tobias@leafnode.se [go_router] Nested stateful navigation with ShellRoute (flutter/packages#2650)
2023-05-22 engine-flutter-autoroll@skia.org Roll Flutter from 077d644 to ab57304 (18 revisions) (flutter/packages#4051)
2023-05-19 engine-flutter-autoroll@skia.org Roll Flutter from 5ae6438 to 077d644 (23 revisions) (flutter/packages#4043)
2023-05-19 stuartmorgan@google.com [local_auth] Migrate iOS to Pigeon (flutter/packages#3974)
2023-05-19 hamadyalghanim@gmail.com [go_router] fix context extension for replaceNamed (flutter/packages#3927)
2023-05-19 JeroenWeener@users.noreply.github.com [image_picker] Fix crash due to `SecurityException` (flutter/packages#4004)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com,rmistry@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
nploi pushed a commit to nploi/packages that referenced this pull request Jul 16, 2023
Updates the platform communication to use Pigeon. This includes some changes to the API boundary:
- Collected authentication options into an options object; this avoids having a lot of positional boolean arguments (since Pigeon doesn't currently support named arguments).
- Collected strings into a strings object, since having them as individual parameters would have been extremely messy.
- Changes the `authenticate` return from a bool+exceptions to an enum that encompasses all of the explicitly known failure modes. To avoid a breaking change for clients, the Dart code creates `PlatformException`s that match the old ones, but this will make it much easier to someday make a (cross-platform) breaking change to replace them with better errors per the best practices we have documented on the wiki. Using an enum rather than throwing errors avoids the need to do string matching when we want to eventually translate them into something other than `PlatformException`.

The Pigeon file, Dart implementation, and rewritten tests are all based very heavily on the recent Android migration: flutter#3748

I noted several bugs that I noticed in the existing implementation during the migration. I intentionally didn't fix them so this isn't changing behavior at the same time that it's changing so much of the code and tests, but I marked them with TODO comments.

Fixes flutter/flutter#117912
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: local_auth platform-ios
Projects
None yet
3 participants