[url_launcher] Convert iOS to Pigeon (#3481)
[url_launcher] Convert iOS to Pigeon
diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
index 26e9c78..0af8882 100644
--- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.1.3
+
+* Switches to Pigeon for internal implementation.
+
## 6.1.2
* Clarifies explanation of endorsement in README.
diff --git a/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.m b/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.m
index 6507a95..64a1799 100644
--- a/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.m
+++ b/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.m
@@ -2,17 +2,156 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+@import Flutter;
@import url_launcher_ios;
@import XCTest;
+@interface FULFakeLauncher : NSObject <FULLauncher>
+@property(copy, nonatomic) NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *passedOptions;
+@end
+
+@implementation FULFakeLauncher
+- (BOOL)canOpenURL:(NSURL *)url {
+ return [url.scheme isEqualToString:@"good"];
+}
+
+- (void)openURL:(NSURL *)url
+ options:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options
+ completionHandler:(void (^__nullable)(BOOL success))completion {
+ self.passedOptions = options;
+ completion([url.scheme isEqualToString:@"good"]);
+}
+@end
+
+#pragma mark -
+
@interface URLLauncherTests : XCTestCase
@end
@implementation URLLauncherTests
-- (void)testPlugin {
- FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init];
- XCTAssertNotNil(plugin);
+- (void)testCanLaunchSuccess {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+
+ FlutterError *error;
+ NSNumber *result = [plugin canLaunchURL:@"good://url" error:&error];
+
+ XCTAssertTrue(result.boolValue);
+ XCTAssertNil(error);
+}
+
+- (void)testCanLaunchFailure {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+
+ FlutterError *error;
+ NSNumber *result = [plugin canLaunchURL:@"bad://url" error:&error];
+
+ XCTAssertNotNil(result);
+ XCTAssertFalse(result.boolValue);
+ XCTAssertNil(error);
+}
+
+- (void)testCanLaunchInvalidURL {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+
+ FlutterError *error;
+ NSNumber *result = [plugin canLaunchURL:@"urls can't have spaces" error:&error];
+
+ XCTAssertNil(result);
+ XCTAssertEqualObjects(error.code, @"argument_error");
+ XCTAssertEqualObjects(error.message, @"Unable to parse URL");
+ XCTAssertEqualObjects(error.details, @"Provided URL: urls can't have spaces");
+}
+
+- (void)testLaunchSuccess {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ [plugin launchURL:@"good://url"
+ universalLinksOnly:@NO
+ completion:^(NSNumber *_Nullable result, FlutterError *_Nullable error) {
+ XCTAssertTrue(result.boolValue);
+ XCTAssertNil(error);
+ [resultExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+}
+
+- (void)testLaunchFailure {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ [plugin launchURL:@"bad://url"
+ universalLinksOnly:@NO
+ completion:^(NSNumber *_Nullable result, FlutterError *_Nullable error) {
+ XCTAssertNotNil(result);
+ XCTAssertFalse(result.boolValue);
+ XCTAssertNil(error);
+ [resultExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+}
+
+- (void)testLaunchInvalidURL {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ [plugin launchURL:@"urls can't have spaces"
+ universalLinksOnly:@NO
+ completion:^(NSNumber *_Nullable result, FlutterError *_Nullable error) {
+ XCTAssertNil(result);
+ XCTAssertNotNil(error);
+ XCTAssertEqualObjects(error.code, @"argument_error");
+ XCTAssertEqualObjects(error.message, @"Unable to parse URL");
+ XCTAssertEqualObjects(error.details, @"Provided URL: urls can't have spaces");
+ [resultExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+}
+
+- (void)testLaunchWithoutUniversalLinks {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ FlutterError *error;
+ [plugin launchURL:@"good://url"
+ universalLinksOnly:@NO
+ completion:^(NSNumber *_Nullable result, FlutterError *_Nullable error) {
+ [resultExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+ XCTAssertNil(error);
+ XCTAssertFalse(
+ ((NSNumber *)launcher.passedOptions[UIApplicationOpenURLOptionUniversalLinksOnly]).boolValue);
+}
+
+- (void)testLaunchWithUniversalLinks {
+ FULFakeLauncher *launcher = [[FULFakeLauncher alloc] init];
+ FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] initWithLauncher:launcher];
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ FlutterError *error;
+ [plugin launchURL:@"good://url"
+ universalLinksOnly:@YES
+ completion:^(NSNumber *_Nullable result, FlutterError *_Nullable error) {
+ [resultExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+ XCTAssertNil(error);
+ XCTAssertTrue(
+ ((NSNumber *)launcher.passedOptions[UIApplicationOpenURLOptionUniversalLinksOnly]).boolValue);
}
@end
diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
index ebfb6e8..89c77b9 100644
--- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
@@ -25,7 +25,6 @@
sdk: flutter
integration_test:
sdk: flutter
- mockito: 5.3.2
plugin_platform_interface: ^2.0.0
flutter:
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h
index 73589d2..7b3480e 100644
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h
@@ -4,5 +4,7 @@
#import <Flutter/Flutter.h>
-@interface FLTURLLauncherPlugin : NSObject <FlutterPlugin>
+#import "messages.g.h"
+
+@interface FLTURLLauncherPlugin : NSObject <FlutterPlugin, FULUrlLauncherApi>
@end
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
index 375d5e2..5d6a75f 100644
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
@@ -5,10 +5,15 @@
#import <SafariServices/SafariServices.h>
#import "FLTURLLauncherPlugin.h"
+#import "FLTURLLauncherPlugin_Test.h"
+#import "FULLauncher.h"
+#import "messages.g.h"
+
+typedef void (^OpenInSafariVCResponse)(NSNumber *_Nullable, FlutterError *_Nullable);
@interface FLTURLLaunchSession : NSObject <SFSafariViewControllerDelegate>
-@property(copy, nonatomic) FlutterResult flutterResult;
+@property(copy, nonatomic) OpenInSafariVCResponse completion;
@property(strong, nonatomic) NSURL *url;
@property(strong, nonatomic) SFSafariViewController *safari;
@property(nonatomic, copy) void (^didFinish)(void);
@@ -17,11 +22,11 @@
@implementation FLTURLLaunchSession
-- (instancetype)initWithUrl:url withFlutterResult:result {
+- (instancetype)initWithURL:url completion:completion {
self = [super init];
if (self) {
self.url = url;
- self.flutterResult = result;
+ self.completion = completion;
self.safari = [[SFSafariViewController alloc] initWithURL:url];
self.safari.delegate = self;
}
@@ -31,12 +36,13 @@
- (void)safariViewController:(SFSafariViewController *)controller
didCompleteInitialLoad:(BOOL)didLoadSuccessfully {
if (didLoadSuccessfully) {
- self.flutterResult(@YES);
+ self.completion(@YES, nil);
} else {
- self.flutterResult([FlutterError
- errorWithCode:@"Error"
- message:[NSString stringWithFormat:@"Error while launching %@", self.url]
- details:nil]);
+ self.completion(
+ nil, [FlutterError
+ errorWithCode:@"Error"
+ message:[NSString stringWithFormat:@"Error while launching %@", self.url]
+ details:nil]);
}
}
@@ -51,64 +57,86 @@
@end
+#pragma mark -
+
+/// Default implementation of FULLancher, using UIApplication.
+@interface FULUIApplicationLauncher : NSObject <FULLauncher>
+@end
+
+@implementation FULUIApplicationLauncher
+- (BOOL)canOpenURL:(nonnull NSURL *)url {
+ return [[UIApplication sharedApplication] canOpenURL:url];
+}
+
+- (void)openURL:(nonnull NSURL *)url
+ options:(nonnull NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options
+ completionHandler:(void (^_Nullable)(BOOL))completion {
+ [[UIApplication sharedApplication] openURL:url options:options completionHandler:completion];
+}
+
+@end
+
+#pragma mark -
+
@interface FLTURLLauncherPlugin ()
@property(strong, nonatomic) FLTURLLaunchSession *currentSession;
+@property(strong, nonatomic) NSObject<FULLauncher> *launcher;
@end
@implementation FLTURLLauncherPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
- FlutterMethodChannel *channel =
- [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/url_launcher_ios"
- binaryMessenger:registrar.messenger];
FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init];
- [registrar addMethodCallDelegate:plugin channel:channel];
+ FULUrlLauncherApiSetup(registrar.messenger, plugin);
}
-- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
- NSString *url = call.arguments[@"url"];
- if ([@"canLaunch" isEqualToString:call.method]) {
- result(@([self canLaunchURL:url]));
- } else if ([@"launch" isEqualToString:call.method]) {
- NSNumber *useSafariVC = call.arguments[@"useSafariVC"];
- if (useSafariVC.boolValue) {
- [self launchURLInVC:url result:result];
- } else {
- [self launchURL:url call:call result:result];
- }
- } else if ([@"closeWebView" isEqualToString:call.method]) {
- [self closeWebViewWithResult:result];
- } else {
- result(FlutterMethodNotImplemented);
+- (instancetype)init {
+ return [self initWithLauncher:[[FULUIApplicationLauncher alloc] init]];
+}
+
+- (instancetype)initWithLauncher:(NSObject<FULLauncher> *)launcher {
+ if (self = [super init]) {
+ _launcher = launcher;
}
+ return self;
}
-- (BOOL)canLaunchURL:(NSString *)urlString {
+- (nullable NSNumber *)canLaunchURL:(NSString *)urlString
+ error:(FlutterError *_Nullable *_Nonnull)error {
NSURL *url = [NSURL URLWithString:urlString];
- UIApplication *application = [UIApplication sharedApplication];
- return [application canOpenURL:url];
+ if (!url) {
+ *error = [self invalidURLErrorForURLString:urlString];
+ return nil;
+ }
+ return @([self.launcher canOpenURL:url]);
}
- (void)launchURL:(NSString *)urlString
- call:(FlutterMethodCall *)call
- result:(FlutterResult)result {
+ universalLinksOnly:(NSNumber *)universalLinksOnly
+ completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion {
NSURL *url = [NSURL URLWithString:urlString];
- UIApplication *application = [UIApplication sharedApplication];
-
- NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0;
+ if (!url) {
+ completion(nil, [self invalidURLErrorForURLString:urlString]);
+ return;
+ }
NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly};
- [application openURL:url
- options:options
- completionHandler:^(BOOL success) {
- result(@(success));
- }];
+ [self.launcher openURL:url
+ options:options
+ completionHandler:^(BOOL success) {
+ completion(@(success), nil);
+ }];
}
-- (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result {
+- (void)openSafariViewControllerWithURL:(NSString *)urlString
+ completion:(OpenInSafariVCResponse)completion {
NSURL *url = [NSURL URLWithString:urlString];
- self.currentSession = [[FLTURLLaunchSession alloc] initWithUrl:url withFlutterResult:result];
+ if (!url) {
+ completion(nil, [self invalidURLErrorForURLString:urlString]);
+ return;
+ }
+ self.currentSession = [[FLTURLLaunchSession alloc] initWithURL:url completion:completion];
__weak typeof(self) weakSelf = self;
self.currentSession.didFinish = ^(void) {
weakSelf.currentSession = nil;
@@ -118,11 +146,8 @@
completion:nil];
}
-- (void)closeWebViewWithResult:(FlutterResult)result {
- if (self.currentSession != nil) {
- [self.currentSession close];
- }
- result(nil);
+- (void)closeSafariViewControllerWithError:(FlutterError *_Nullable *_Nonnull)error {
+ [self.currentSession close];
}
- (UIViewController *)topViewController {
@@ -162,4 +187,16 @@
}
return viewController;
}
+
+/**
+ * Creates an error for an invalid URL string.
+ *
+ * @param url The invalid URL string
+ * @return The error to return
+ */
+- (FlutterError *)invalidURLErrorForURLString:(NSString *)url {
+ return [FlutterError errorWithCode:@"argument_error"
+ message:@"Unable to parse URL"
+ details:[NSString stringWithFormat:@"Provided URL: %@", url]];
+}
@end
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h
new file mode 100644
index 0000000..112682a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h
@@ -0,0 +1,11 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FLTURLLauncherPlugin.h"
+#import "FULLauncher.h"
+
+/// APIs exposed for testing.
+@interface FLTURLLauncherPlugin (Test)
+- (instancetype)initWithLauncher:(NSObject<FULLauncher> *)launcher;
+@end
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h
new file mode 100644
index 0000000..63f8e04
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Protocol for UIApplication methods relating to launching URLs.
+///
+/// This protocol exists to allow injecting an alternate implementation for testing.
+@protocol FULLauncher
+- (BOOL)canOpenURL:(NSURL *)url;
+- (void)openURL:(NSURL *)url
+ options:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options
+ completionHandler:(void (^__nullable)(BOOL success))completion;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h
new file mode 100644
index 0000000..9208920
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h
@@ -0,0 +1,40 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v9.0.7), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+#import <Foundation/Foundation.h>
+
+@protocol FlutterBinaryMessenger;
+@protocol FlutterMessageCodec;
+@class FlutterError;
+@class FlutterStandardTypedData;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// The codec used by FULUrlLauncherApi.
+NSObject<FlutterMessageCodec> *FULUrlLauncherApiGetCodec(void);
+
+@protocol FULUrlLauncherApi
+/// Returns true if the URL can definitely be launched.
+///
+/// @return `nil` only when `error != nil`.
+- (nullable NSNumber *)canLaunchURL:(NSString *)url error:(FlutterError *_Nullable *_Nonnull)error;
+/// Opens the URL externally, returning true if successful.
+- (void)launchURL:(NSString *)url
+ universalLinksOnly:(NSNumber *)universalLinksOnly
+ completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;
+/// Opens the URL in an in-app SFSafariViewController, returning true
+/// when it has loaded successfully.
+- (void)openSafariViewControllerWithURL:(NSString *)url
+ completion:
+ (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;
+/// Closes the view controller opened by [openUrlInSafariViewController].
+- (void)closeSafariViewControllerWithError:(FlutterError *_Nullable *_Nonnull)error;
+@end
+
+extern void FULUrlLauncherApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
+ NSObject<FULUrlLauncherApi> *_Nullable api);
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m
new file mode 100644
index 0000000..5c655bd
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m
@@ -0,0 +1,126 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v9.0.7), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+#import "messages.g.h"
+#import <Flutter/Flutter.h>
+
+#if !__has_feature(objc_arc)
+#error File requires ARC to be enabled.
+#endif
+
+static NSArray *wrapResult(id result, FlutterError *error) {
+ if (error) {
+ return @[
+ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null]
+ ];
+ }
+ return @[ result ?: [NSNull null] ];
+}
+static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
+ id result = array[key];
+ return (result == [NSNull null]) ? nil : result;
+}
+
+NSObject<FlutterMessageCodec> *FULUrlLauncherApiGetCodec() {
+ static FlutterStandardMessageCodec *sSharedObject = nil;
+ sSharedObject = [FlutterStandardMessageCodec sharedInstance];
+ return sSharedObject;
+}
+
+void FULUrlLauncherApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
+ NSObject<FULUrlLauncherApi> *api) {
+ /// Returns true if the URL can definitely be launched.
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl"
+ binaryMessenger:binaryMessenger
+ codec:FULUrlLauncherApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(canLaunchURL:error:)],
+ @"FULUrlLauncherApi api (%@) doesn't respond to @selector(canLaunchURL:error:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ NSString *arg_url = GetNullableObjectAtIndex(args, 0);
+ FlutterError *error;
+ NSNumber *output = [api canLaunchURL:arg_url error:&error];
+ callback(wrapResult(output, error));
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ /// Opens the URL externally, returning true if successful.
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.UrlLauncherApi.launchUrl"
+ binaryMessenger:binaryMessenger
+ codec:FULUrlLauncherApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(launchURL:universalLinksOnly:completion:)],
+ @"FULUrlLauncherApi api (%@) doesn't respond to "
+ @"@selector(launchURL:universalLinksOnly:completion:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ NSString *arg_url = GetNullableObjectAtIndex(args, 0);
+ NSNumber *arg_universalLinksOnly = GetNullableObjectAtIndex(args, 1);
+ [api launchURL:arg_url
+ universalLinksOnly:arg_universalLinksOnly
+ completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) {
+ callback(wrapResult(output, error));
+ }];
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ /// Opens the URL in an in-app SFSafariViewController, returning true
+ /// when it has loaded successfully.
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.UrlLauncherApi.openUrlInSafariViewController"
+ binaryMessenger:binaryMessenger
+ codec:FULUrlLauncherApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(openSafariViewControllerWithURL:completion:)],
+ @"FULUrlLauncherApi api (%@) doesn't respond to "
+ @"@selector(openSafariViewControllerWithURL:completion:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ NSString *arg_url = GetNullableObjectAtIndex(args, 0);
+ [api openSafariViewControllerWithURL:arg_url
+ completion:^(NSNumber *_Nullable output,
+ FlutterError *_Nullable error) {
+ callback(wrapResult(output, error));
+ }];
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ /// Closes the view controller opened by [openUrlInSafariViewController].
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.UrlLauncherApi.closeSafariViewController"
+ binaryMessenger:binaryMessenger
+ codec:FULUrlLauncherApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(closeSafariViewControllerWithError:)],
+ @"FULUrlLauncherApi api (%@) doesn't respond to "
+ @"@selector(closeSafariViewControllerWithError:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ FlutterError *error;
+ [api closeSafariViewControllerWithError:&error];
+ callback(wrapResult(nil, error));
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+}
diff --git a/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart
new file mode 100644
index 0000000..43ec1ed
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart
@@ -0,0 +1,131 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v9.0.7), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+
+class UrlLauncherApi {
+ /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is
+ /// available for dependency injection. If it is left null, the default
+ /// BinaryMessenger will be used which routes to the host platform.
+ UrlLauncherApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = StandardMessageCodec();
+
+ /// Returns true if the URL can definitely be launched.
+ Future<bool> canLaunchUrl(String arg_url) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList =
+ await channel.send(<Object?>[arg_url]) as List<Object?>?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as bool?)!;
+ }
+ }
+
+ /// Opens the URL externally, returning true if successful.
+ Future<bool> launchUrl(String arg_url, bool arg_universalLinksOnly) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList = await channel
+ .send(<Object?>[arg_url, arg_universalLinksOnly]) as List<Object?>?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as bool?)!;
+ }
+ }
+
+ /// Opens the URL in an in-app SFSafariViewController, returning true
+ /// when it has loaded successfully.
+ Future<bool> openUrlInSafariViewController(String arg_url) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.UrlLauncherApi.openUrlInSafariViewController',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList =
+ await channel.send(<Object?>[arg_url]) as List<Object?>?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else if (replyList[0] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyList[0] as bool?)!;
+ }
+ }
+
+ /// Closes the view controller opened by [openUrlInSafariViewController].
+ Future<void> closeSafariViewController() async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.UrlLauncherApi.closeSafariViewController', codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
+ if (replyList == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyList.length > 1) {
+ throw PlatformException(
+ code: replyList[0]! as String,
+ message: replyList[1] as String?,
+ details: replyList[2],
+ );
+ } else {
+ return;
+ }
+ }
+}
diff --git a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart
index 84b811b..2f0e9f4 100644
--- a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart
+++ b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart
@@ -2,17 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:async';
-
-import 'package:flutter/services.dart';
+import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
-const MethodChannel _channel =
- MethodChannel('plugins.flutter.io/url_launcher_ios');
+import 'src/messages.g.dart';
/// An implementation of [UrlLauncherPlatform] for iOS.
class UrlLauncherIOS extends UrlLauncherPlatform {
+ /// Creates a new plugin implementation instance.
+ UrlLauncherIOS({
+ @visibleForTesting UrlLauncherApi? api,
+ }) : _hostApi = api ?? UrlLauncherApi();
+
+ final UrlLauncherApi _hostApi;
+
/// Registers this class as the default instance of [UrlLauncherPlatform].
static void registerWith() {
UrlLauncherPlatform.instance = UrlLauncherIOS();
@@ -23,15 +27,12 @@
@override
Future<bool> canLaunch(String url) {
- return _channel.invokeMethod<bool>(
- 'canLaunch',
- <String, Object>{'url': url},
- ).then((bool? value) => value ?? false);
+ return _hostApi.canLaunchUrl(url);
}
@override
Future<void> closeWebView() {
- return _channel.invokeMethod<void>('closeWebView');
+ return _hostApi.closeSafariViewController();
}
@override
@@ -45,16 +46,10 @@
required Map<String, String> headers,
String? webOnlyWindowName,
}) {
- return _channel.invokeMethod<bool>(
- 'launch',
- <String, Object>{
- 'url': url,
- 'useSafariVC': useSafariVC,
- 'enableJavaScript': enableJavaScript,
- 'enableDomStorage': enableDomStorage,
- 'universalLinksOnly': universalLinksOnly,
- 'headers': headers,
- },
- ).then((bool? value) => value ?? false);
+ if (useSafariVC) {
+ return _hostApi.openUrlInSafariViewController(url);
+ } else {
+ return _hostApi.launchUrl(url, universalLinksOnly);
+ }
}
}
diff --git a/packages/url_launcher/url_launcher_ios/pigeons/copyright.txt b/packages/url_launcher/url_launcher_ios/pigeons/copyright.txt
new file mode 100644
index 0000000..1236b63
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/pigeons/copyright.txt
@@ -0,0 +1,3 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
diff --git a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart
new file mode 100644
index 0000000..f6935cb
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart
@@ -0,0 +1,33 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/pigeon.dart';
+
+@ConfigurePigeon(PigeonOptions(
+ dartOut: 'lib/src/messages.g.dart',
+ objcOptions: ObjcOptions(prefix: 'FUL'),
+ objcHeaderOut: 'ios/Classes/messages.g.h',
+ objcSourceOut: 'ios/Classes/messages.g.m',
+ copyrightHeader: 'pigeons/copyright.txt',
+))
+@HostApi()
+abstract class UrlLauncherApi {
+ /// Returns true if the URL can definitely be launched.
+ @ObjCSelector('canLaunchURL:')
+ bool canLaunchUrl(String url);
+
+ /// Opens the URL externally, returning true if successful.
+ @async
+ @ObjCSelector('launchURL:universalLinksOnly:')
+ bool launchUrl(String url, bool universalLinksOnly);
+
+ /// Opens the URL in an in-app SFSafariViewController, returning true
+ /// when it has loaded successfully.
+ @async
+ @ObjCSelector('openSafariViewControllerWithURL:')
+ bool openUrlInSafariViewController(String url);
+
+ /// Closes the view controller opened by [openUrlInSafariViewController].
+ void closeSafariViewController();
+}
diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml
index 3cdceb2..f4b3e3a 100644
--- a/packages/url_launcher/url_launcher_ios/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml
@@ -2,7 +2,7 @@
description: iOS implementation of the url_launcher plugin.
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.1.2
+version: 6.1.3
environment:
sdk: '>=2.18.0 <3.0.0'
@@ -24,6 +24,6 @@
dev_dependencies:
flutter_test:
sdk: flutter
- mockito: 5.3.2
+ pigeon: ^9.0.7
plugin_platform_interface: ^2.0.0
test: ^1.16.3
diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart
index 34dac1c..f87859e 100644
--- a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart
+++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart
@@ -4,28 +4,18 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_ios/src/messages.g.dart';
import 'package:url_launcher_ios/url_launcher_ios.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
- group('$UrlLauncherIOS', () {
- const MethodChannel channel =
- MethodChannel('plugins.flutter.io/url_launcher_ios');
- final List<MethodCall> log = <MethodCall>[];
- _ambiguate(TestDefaultBinaryMessengerBinding.instance)!
- .defaultBinaryMessenger
- .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
- log.add(methodCall);
+ group('UrlLauncherIOS', () {
+ late _FakeUrlLauncherApi api;
- // Return null explicitly instead of relying on the implicit null
- // returned by the method channel if no return statement is specified.
- return null;
- });
-
- tearDown(() {
- log.clear();
+ setUp(() {
+ api = _FakeUrlLauncherApi();
});
test('registers instance', () {
@@ -33,184 +23,167 @@
expect(UrlLauncherPlatform.instance, isA<UrlLauncherIOS>());
});
- test('canLaunch', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.canLaunch('http://example.com/');
- expect(
- log,
- <Matcher>[
- isMethodCall('canLaunch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- })
- ],
- );
+ test('canLaunch success', () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ expect(await launcher.canLaunch('http://example.com/'), true);
});
- test('canLaunch should return false if platform returns null', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- final bool canLaunch = await launcher.canLaunch('http://example.com/');
-
- expect(canLaunch, false);
+ test('canLaunch failure', () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ expect(await launcher.canLaunch('unknown://scheme'), false);
});
- test('launch', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.launch(
- 'http://example.com/',
- useSafariVC: true,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: const <String, String>{},
- );
- expect(
- log,
- <Matcher>[
- isMethodCall('launch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- 'useSafariVC': true,
- 'enableJavaScript': false,
- 'enableDomStorage': false,
- 'universalLinksOnly': false,
- 'headers': <String, String>{},
- })
- ],
- );
+ test('canLaunch invalid URL passes the PlatformException through',
+ () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ expectLater(launcher.canLaunch('invalid://u r l'),
+ throwsA(isA<PlatformException>()));
});
- test('launch with headers', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.launch(
- 'http://example.com/',
- useSafariVC: true,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: const <String, String>{'key': 'value'},
- );
+ test('launch success', () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
- log,
- <Matcher>[
- isMethodCall('launch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- 'useSafariVC': true,
- 'enableJavaScript': false,
- 'enableDomStorage': false,
- 'universalLinksOnly': false,
- 'headers': <String, String>{'key': 'value'},
- })
- ],
- );
+ await launcher.launch(
+ 'http://example.com/',
+ useSafariVC: false,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: false,
+ headers: const <String, String>{},
+ ),
+ true);
+ expect(api.passedUniversalLinksOnly, false);
+ });
+
+ test('launch failure', () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ expect(
+ await launcher.launch(
+ 'unknown://scheme',
+ useSafariVC: false,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: false,
+ headers: const <String, String>{},
+ ),
+ false);
+ expect(api.passedUniversalLinksOnly, false);
+ });
+
+ test('launch invalid URL passes the PlatformException through', () async {
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ expectLater(
+ launcher.launch(
+ 'invalid://u r l',
+ useSafariVC: false,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: false,
+ headers: const <String, String>{},
+ ),
+ throwsA(isA<PlatformException>()));
});
test('launch force SafariVC', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.launch(
- 'http://example.com/',
- useSafariVC: true,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: const <String, String>{},
- );
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
- log,
- <Matcher>[
- isMethodCall('launch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- 'useSafariVC': true,
- 'enableJavaScript': false,
- 'enableDomStorage': false,
- 'universalLinksOnly': false,
- 'headers': <String, String>{},
- })
- ],
- );
+ await launcher.launch(
+ 'http://example.com/',
+ useSafariVC: true,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: false,
+ headers: const <String, String>{},
+ ),
+ true);
+ expect(api.usedSafariViewController, true);
});
test('launch universal links only', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.launch(
- 'http://example.com/',
- useSafariVC: false,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: true,
- headers: const <String, String>{},
- );
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
- log,
- <Matcher>[
- isMethodCall('launch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- 'useSafariVC': false,
- 'enableJavaScript': false,
- 'enableDomStorage': false,
- 'universalLinksOnly': true,
- 'headers': <String, String>{},
- })
- ],
- );
+ await launcher.launch(
+ 'http://example.com/',
+ useSafariVC: false,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: true,
+ headers: const <String, String>{},
+ ),
+ true);
+ expect(api.passedUniversalLinksOnly, true);
});
test('launch force SafariVC to false', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- await launcher.launch(
- 'http://example.com/',
- useSafariVC: false,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: const <String, String>{},
- );
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
- log,
- <Matcher>[
- isMethodCall('launch', arguments: <String, Object>{
- 'url': 'http://example.com/',
- 'useSafariVC': false,
- 'enableJavaScript': false,
- 'enableDomStorage': false,
- 'universalLinksOnly': false,
- 'headers': <String, String>{},
- })
- ],
- );
- });
-
- test('launch should return false if platform returns null', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
- final bool launched = await launcher.launch(
- 'http://example.com/',
- useSafariVC: true,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: const <String, String>{},
- );
-
- expect(launched, false);
+ await launcher.launch(
+ 'http://example.com/',
+ useSafariVC: false,
+ useWebView: false,
+ enableJavaScript: false,
+ enableDomStorage: false,
+ universalLinksOnly: false,
+ headers: const <String, String>{},
+ ),
+ true);
+ expect(api.usedSafariViewController, false);
});
test('closeWebView default behavior', () async {
- final UrlLauncherIOS launcher = UrlLauncherIOS();
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await launcher.closeWebView();
- expect(
- log,
- <Matcher>[isMethodCall('closeWebView', arguments: null)],
- );
+ expect(api.closed, true);
});
});
}
-/// This allows a value of type T or T? to be treated as a value of type T?.
+/// A fake implementation of the host API that reacts to specific schemes.
///
-/// We use this so that APIs that have become non-nullable can still be used
-/// with `!` and `?` on the stable branch.
-T? _ambiguate<T>(T? value) => value;
+/// See _isLaunchable for the behaviors.
+class _FakeUrlLauncherApi implements UrlLauncherApi {
+ bool? passedUniversalLinksOnly;
+ bool? usedSafariViewController;
+ bool? closed;
+
+ @override
+ Future<bool> canLaunchUrl(String url) async {
+ return _isLaunchable(url);
+ }
+
+ @override
+ Future<bool> launchUrl(String url, bool universalLinksOnly) async {
+ passedUniversalLinksOnly = universalLinksOnly;
+ usedSafariViewController = false;
+ return _isLaunchable(url);
+ }
+
+ @override
+ Future<bool> openUrlInSafariViewController(String url) async {
+ usedSafariViewController = true;
+ return _isLaunchable(url);
+ }
+
+ @override
+ Future<void> closeSafariViewController() async {
+ closed = true;
+ }
+
+ bool _isLaunchable(String url) {
+ final String scheme = url.split(':')[0];
+ switch (scheme) {
+ case 'http':
+ case 'https':
+ return true;
+ case 'invalid':
+ throw PlatformException(code: 'argument_error');
+ default:
+ return false;
+ }
+ }
+}