[url_launcher] migrating objc plugin to swift (#4753)
This PR converts the iOS portion of the url_launcher plugin from objc to swift.
*List which issues are fixed by this PR. You must list at least one issue.*
https://github.com/flutter/flutter/issues/119102
diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
index ae63012..4e9d077 100644
--- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.2.1
+
+* Migrates plugin from Objective-C to Swift.
+
## 6.2.0
* Implements `supportsMode` and `supportsCloseForMode`.
diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj
index e40cd34..c10bff1 100644
--- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj
@@ -269,7 +269,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1300;
+ LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "The Flutter Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -631,6 +631,7 @@
baseConfigurationReference = 666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -651,6 +652,7 @@
baseConfigurationReference = D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index ad0ebfa..fa4e0bb 100644
--- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
- LastUpgradeVersion = "1300"
+ LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
diff --git a/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.swift b/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.swift
index e11a7e3..966a3d6 100644
--- a/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.swift
+++ b/packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.swift
@@ -7,59 +7,52 @@
@testable import url_launcher_ios
+// Tests whether NSURL parsing is strict. When linking against the iOS 17 SDK or later,
+// NSURL uses a more lenient parser which will not return nil.
+private func urlParsingIsStrict() -> Bool {
+ return URL(string: "b a d U R L") == nil
+}
+
final class URLLauncherTests: XCTestCase {
- private func createPlugin() -> FLTURLLauncherPlugin {
+ private func createPlugin() -> URLLauncherPlugin {
let launcher = FakeLauncher()
- return FLTURLLauncherPlugin(launcher: launcher)
+ return URLLauncherPlugin(launcher: launcher)
}
- private func createPlugin(launcher: FakeLauncher) -> FLTURLLauncherPlugin {
- FLTURLLauncherPlugin(launcher: launcher)
+ private func createPlugin(launcher: FakeLauncher) -> URLLauncherPlugin {
+ return URLLauncherPlugin(launcher: launcher)
}
func testCanLaunchSuccess() {
- var error: FlutterError?
- let result = createPlugin().canLaunchURL("good://url", error: &error)
-
- XCTAssertNotNil(result)
- XCTAssertTrue(result?.boolValue ?? false)
- XCTAssertNil(error)
+ let result = createPlugin().canLaunchUrl(url: "good://url")
+ XCTAssertEqual(result, .success)
}
func testCanLaunchFailure() {
- var error: FlutterError?
- let result = createPlugin().canLaunchURL("bad://url", error: &error)
-
- XCTAssertNotNil(result)
- XCTAssertFalse(result?.boolValue ?? true)
+ let result = createPlugin().canLaunchUrl(url: "bad://url")
+ XCTAssertEqual(result, .failure)
}
func testCanLaunchFailureWithInvalidURL() {
- var error: FlutterError?
- let result = createPlugin().canLaunchURL("urls can't have spaces", error: &error)
+ let result = createPlugin().canLaunchUrl(url: "urls can't have spaces")
- if (error == nil) {
- // When linking against the iOS 17 SDK or later, NSURL uses a lenient parser, and won't
- // fail to parse URLs, so the test must allow for either outcome.
- XCTAssertNotNil(result)
- XCTAssertFalse(result?.boolValue ?? true)
- XCTAssertNil(error)
+ if urlParsingIsStrict() {
+ XCTAssertEqual(result, .invalidUrl)
} else {
- XCTAssertNil(result)
- XCTAssertNotNil(error)
- XCTAssertEqual(error?.code, "argument_error")
- XCTAssertEqual(error?.message, "Unable to parse URL")
- XCTAssertEqual(error?.details as? String, "Provided URL: urls can't have spaces")
+ XCTAssertEqual(result, .failure)
}
}
func testLaunchSuccess() {
let expectation = XCTestExpectation(description: "completion called")
- createPlugin().launchURL("good://url", universalLinksOnly: false) { result, error in
- XCTAssertNotNil(result)
- XCTAssertTrue(result?.boolValue ?? false)
- XCTAssertNil(error)
+ createPlugin().launchUrl(url: "good://url", universalLinksOnly: false) { result in
+ switch result {
+ case .success(let details):
+ XCTAssertEqual(details, .success)
+ case .failure(let error):
+ XCTFail("Unexpected error: \(error)")
+ }
expectation.fulfill()
}
@@ -68,11 +61,13 @@
func testLaunchFailure() {
let expectation = XCTestExpectation(description: "completion called")
-
- createPlugin().launchURL("bad://url", universalLinksOnly: false) { result, error in
- XCTAssertNotNil(result)
- XCTAssertFalse(result?.boolValue ?? true)
- XCTAssertNil(error)
+ createPlugin().launchUrl(url: "bad://url", universalLinksOnly: false) { result in
+ switch result {
+ case .success(let details):
+ XCTAssertEqual(details, .failure)
+ case .failure(let error):
+ XCTFail("Unexpected error: \(error)")
+ }
expectation.fulfill()
}
@@ -81,22 +76,17 @@
func testLaunchFailureWithInvalidURL() {
let expectation = XCTestExpectation(description: "completion called")
-
- createPlugin().launchURL("urls can't have spaces", universalLinksOnly: false) { result, error in
- if (error == nil) {
- // When linking against the iOS 17 SDK or later, NSURL uses a lenient parser, and won't
- // fail to parse URLs, so the test must allow for either outcome.
- XCTAssertNotNil(result)
- XCTAssertFalse(result?.boolValue ?? true)
- XCTAssertNil(error)
- } else {
- XCTAssertNil(result)
- XCTAssertNotNil(error)
- XCTAssertEqual(error?.code, "argument_error")
- XCTAssertEqual(error?.message, "Unable to parse URL")
- XCTAssertEqual(error?.details as? String, "Provided URL: urls can't have spaces")
+ createPlugin().launchUrl(url: "urls can't have spaces", universalLinksOnly: false) { result in
+ switch result {
+ case .success(let details):
+ if urlParsingIsStrict() {
+ XCTAssertEqual(details, .invalidUrl)
+ } else {
+ XCTAssertEqual(details, .failure)
+ }
+ case .failure(let error):
+ XCTFail("Unexpected error: \(error)")
}
-
expectation.fulfill()
}
@@ -108,13 +98,17 @@
let plugin = createPlugin(launcher: launcher)
let expectation = XCTestExpectation(description: "completion called")
- plugin.launchURL("good://url", universalLinksOnly: false) { result, error in
- XCTAssertNil(error)
+ plugin.launchUrl(url: "good://url", universalLinksOnly: false) { result in
+ switch result {
+ case .success(let details):
+ XCTAssertEqual(details, .success)
+ case .failure(let error):
+ XCTFail("Unexpected error: \(error)")
+ }
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
-
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, false)
}
@@ -123,31 +117,35 @@
let plugin = createPlugin(launcher: launcher)
let expectation = XCTestExpectation(description: "completion called")
-
- plugin.launchURL("good://url", universalLinksOnly: true) { result, error in
- XCTAssertNil(error)
+ plugin.launchUrl(url: "good://url", universalLinksOnly: true) { result in
+ switch result {
+ case .success(let details):
+ XCTAssertEqual(details, .success)
+ case .failure(let error):
+ XCTFail("Unexpected error: \(error)")
+ }
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
-
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, true)
}
}
-final private class FakeLauncher: NSObject, FULLauncher {
+final private class FakeLauncher: NSObject, Launcher {
var passedOptions: [UIApplication.OpenExternalURLOptionsKey: Any]?
- func canOpen(_ url: URL) -> Bool {
- return url.scheme == "good"
+ func canOpenURL(_ url: URL) -> Bool {
+ url.scheme == "good"
}
func open(
- _ url: URL, options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:],
- completionHandler: ((Bool) -> Void)? = nil
+ _ url: URL,
+ options: [UIApplication.OpenExternalURLOptionsKey: Any],
+ completionHandler completion: ((Bool) -> Void)?
) {
self.passedOptions = options
- completionHandler?(url.scheme == "good")
+ completion?(url.scheme == "good")
}
}
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h
deleted file mode 100644
index 7b3480e..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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 <Flutter/Flutter.h>
-
-#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
deleted file mode 100644
index 5d6a75f..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
+++ /dev/null
@@ -1,202 +0,0 @@
-// 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 <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) OpenInSafariVCResponse completion;
-@property(strong, nonatomic) NSURL *url;
-@property(strong, nonatomic) SFSafariViewController *safari;
-@property(nonatomic, copy) void (^didFinish)(void);
-
-@end
-
-@implementation FLTURLLaunchSession
-
-- (instancetype)initWithURL:url completion:completion {
- self = [super init];
- if (self) {
- self.url = url;
- self.completion = completion;
- self.safari = [[SFSafariViewController alloc] initWithURL:url];
- self.safari.delegate = self;
- }
- return self;
-}
-
-- (void)safariViewController:(SFSafariViewController *)controller
- didCompleteInitialLoad:(BOOL)didLoadSuccessfully {
- if (didLoadSuccessfully) {
- self.completion(@YES, nil);
- } else {
- self.completion(
- nil, [FlutterError
- errorWithCode:@"Error"
- message:[NSString stringWithFormat:@"Error while launching %@", self.url]
- details:nil]);
- }
-}
-
-- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
- [controller dismissViewControllerAnimated:YES completion:nil];
- self.didFinish();
-}
-
-- (void)close {
- [self safariViewControllerDidFinish:self.safari];
-}
-
-@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 {
- FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init];
- FULUrlLauncherApiSetup(registrar.messenger, plugin);
-}
-
-- (instancetype)init {
- return [self initWithLauncher:[[FULUIApplicationLauncher alloc] init]];
-}
-
-- (instancetype)initWithLauncher:(NSObject<FULLauncher> *)launcher {
- if (self = [super init]) {
- _launcher = launcher;
- }
- return self;
-}
-
-- (nullable NSNumber *)canLaunchURL:(NSString *)urlString
- error:(FlutterError *_Nullable *_Nonnull)error {
- NSURL *url = [NSURL URLWithString:urlString];
- if (!url) {
- *error = [self invalidURLErrorForURLString:urlString];
- return nil;
- }
- return @([self.launcher canOpenURL:url]);
-}
-
-- (void)launchURL:(NSString *)urlString
- universalLinksOnly:(NSNumber *)universalLinksOnly
- completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion {
- NSURL *url = [NSURL URLWithString:urlString];
- if (!url) {
- completion(nil, [self invalidURLErrorForURLString:urlString]);
- return;
- }
- NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly};
- [self.launcher openURL:url
- options:options
- completionHandler:^(BOOL success) {
- completion(@(success), nil);
- }];
-}
-
-- (void)openSafariViewControllerWithURL:(NSString *)urlString
- completion:(OpenInSafariVCResponse)completion {
- NSURL *url = [NSURL URLWithString:urlString];
- 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;
- };
- [self.topViewController presentViewController:self.currentSession.safari
- animated:YES
- completion:nil];
-}
-
-- (void)closeSafariViewControllerWithError:(FlutterError *_Nullable *_Nonnull)error {
- [self.currentSession close];
-}
-
-- (UIViewController *)topViewController {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- // TODO(stuartmorgan) Provide a non-deprecated codepath. See
- // https://github.com/flutter/flutter/issues/104117
- return [self topViewControllerFromViewController:[UIApplication sharedApplication]
- .keyWindow.rootViewController];
-#pragma clang diagnostic pop
-}
-
-/**
- * This method recursively iterate through the view hierarchy
- * to return the top most view controller.
- *
- * It supports the following scenarios:
- *
- * - The view controller is presenting another view.
- * - The view controller is a UINavigationController.
- * - The view controller is a UITabBarController.
- *
- * @return The top most view controller.
- */
-- (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController {
- if ([viewController isKindOfClass:[UINavigationController class]]) {
- UINavigationController *navigationController = (UINavigationController *)viewController;
- return [self
- topViewControllerFromViewController:[navigationController.viewControllers lastObject]];
- }
- if ([viewController isKindOfClass:[UITabBarController class]]) {
- UITabBarController *tabController = (UITabBarController *)viewController;
- return [self topViewControllerFromViewController:tabController.selectedViewController];
- }
- if (viewController.presentedViewController) {
- return [self topViewControllerFromViewController:viewController.presentedViewController];
- }
- 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
deleted file mode 100644
index 112682a..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin_Test.h
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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
deleted file mode 100644
index 63f8e04..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FULLauncher.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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/Launcher.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift
new file mode 100644
index 0000000..f97db9d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/Launcher.swift
@@ -0,0 +1,20 @@
+// 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.
+
+/// Protocol for UIApplication methods relating to launching URLs.
+///
+/// This protocol exists to allow injecting an alternate implementation for testing.
+protocol Launcher {
+ /// Returns a Boolean value that indicates whether an app is available to handle a URL scheme.
+ func canOpenURL(_ url: URL) -> Bool
+
+ /// Attempts to asynchronously open the resource at the specified URL.
+ func open(
+ _ url: URL,
+ options: [UIApplication.OpenExternalURLOptionsKey: Any],
+ completionHandler completion: ((Bool) -> Void)?)
+}
+
+/// Launcher is intentionally a direct passthroguh to UIApplication.
+extension UIApplication: Launcher {}
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift
new file mode 100644
index 0000000..b0761e5
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLaunchSession.swift
@@ -0,0 +1,63 @@
+// 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 Flutter
+import SafariServices
+
+typealias OpenInSafariCompletionHandler = (Result<InAppLoadResult, Error>) -> Void
+
+/// A session responsible for launching a URL in Safari and handling its events.
+final class URLLaunchSession: NSObject, SFSafariViewControllerDelegate {
+
+ private let completion: OpenInSafariCompletionHandler
+ private let url: URL
+
+ /// The Safari view controller used for displaying the URL.
+ let safariViewController: SFSafariViewController
+
+ // A closure to be executed after the Safari view controller finishes.
+ var didFinish: (() -> Void)?
+
+ /// Initializes a new URLLaunchSession with the provided URL and completion handler.
+ ///
+ /// - Parameters:
+ /// - url: The URL to be opened in Safari.
+ /// - completion: The completion handler to be called after attempting to open the URL.
+ init(url: URL, completion: @escaping OpenInSafariCompletionHandler) {
+ self.url = url
+ self.completion = completion
+ self.safariViewController = SFSafariViewController(url: url)
+ super.init()
+ self.safariViewController.delegate = self
+ }
+
+ /// Called when the Safari view controller completes the initial load.
+ ///
+ /// - Parameters:
+ /// - controller: The Safari view controller.
+ /// - didLoadSuccessfully: Indicates if the initial load was successful.
+ func safariViewController(
+ _ controller: SFSafariViewController,
+ didCompleteInitialLoad didLoadSuccessfully: Bool
+ ) {
+ if didLoadSuccessfully {
+ completion(.success(.success))
+ } else {
+ completion(.success(.failedToLoad))
+ }
+ }
+
+ /// Called when the user finishes using the Safari view controller.
+ ///
+ /// - Parameter controller: The Safari view controller.
+ func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
+ controller.dismiss(animated: true, completion: nil)
+ didFinish?()
+ }
+
+ /// Closes the Safari view controller.
+ func close() {
+ safariViewControllerDidFinish(safariViewController)
+ }
+}
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift
new file mode 100644
index 0000000..1880031
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/URLLauncherPlugin.swift
@@ -0,0 +1,99 @@
+// 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 Flutter
+
+public final class URLLauncherPlugin: NSObject, FlutterPlugin, UrlLauncherApi {
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let plugin = URLLauncherPlugin()
+ UrlLauncherApiSetup.setUp(binaryMessenger: registrar.messenger(), api: plugin)
+ registrar.publish(plugin)
+ }
+
+ private var currentSession: URLLaunchSession?
+ private let launcher: Launcher
+
+ private var topViewController: UIViewController? {
+ // TODO(stuartmorgan) Provide a non-deprecated codepath. See
+ // https://github.com/flutter/flutter/issues/104117
+ UIApplication.shared.keyWindow?.rootViewController?.topViewController
+ }
+
+ init(launcher: Launcher = UIApplication.shared) {
+ self.launcher = launcher
+ }
+
+ func canLaunchUrl(url: String) -> LaunchResult {
+ guard let url = URL(string: url) else {
+ return .invalidUrl
+ }
+ let canOpen = launcher.canOpenURL(url)
+ return canOpen ? .success : .failure
+ }
+
+ func launchUrl(
+ url: String,
+ universalLinksOnly: Bool,
+ completion: @escaping (Result<LaunchResult, Error>) -> Void
+ ) {
+ guard let url = URL(string: url) else {
+ completion(.success(.invalidUrl))
+ return
+ }
+ let options = [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: universalLinksOnly]
+ launcher.open(url, options: options) { result in
+ completion(.success(result ? .success : .failure))
+ }
+ }
+
+ func openUrlInSafariViewController(
+ url: String,
+ completion: @escaping (Result<InAppLoadResult, Error>) -> Void
+ ) {
+ guard let url = URL(string: url) else {
+ completion(.success(.invalidUrl))
+ return
+ }
+
+ let session = URLLaunchSession(url: url, completion: completion)
+ currentSession = session
+
+ session.didFinish = { [weak self] in
+ self?.currentSession = nil
+ }
+ topViewController?.present(session.safariViewController, animated: true, completion: nil)
+ }
+
+ func closeSafariViewController() {
+ currentSession?.close()
+ }
+}
+
+/// This method recursively iterates through the view hierarchy
+/// to return the top-most view controller.
+///
+/// It supports the following scenarios:
+///
+/// - The view controller is presenting another view.
+/// - The view controller is a UINavigationController.
+/// - The view controller is a UITabBarController.
+///
+/// @return The top most view controller.
+extension UIViewController {
+ var topViewController: UIViewController {
+ if let navigationController = self as? UINavigationController {
+ return navigationController.viewControllers.last?.topViewController
+ ?? navigationController
+ .visibleViewController ?? navigationController
+ }
+ if let tabBarController = self as? UITabBarController {
+ return tabBarController.selectedViewController?.topViewController ?? tabBarController
+ }
+ if let presented = presentedViewController {
+ return presented.topViewController
+ }
+ return self
+ }
+}
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
deleted file mode 100644
index 3a63e07..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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.2.4), 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
deleted file mode 100644
index 4a38efb..0000000
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.m
+++ /dev/null
@@ -1,126 +0,0 @@
-// 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.2.4), 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(void) {
- 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/ios/Classes/messages.g.swift b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift
new file mode 100644
index 0000000..c3b0b8a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/messages.g.swift
@@ -0,0 +1,163 @@
+// 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 (v11.0.1), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+import Foundation
+
+#if os(iOS)
+ import Flutter
+#elseif os(macOS)
+ import FlutterMacOS
+#else
+ #error("Unsupported platform.")
+#endif
+
+private func isNullish(_ value: Any?) -> Bool {
+ return value is NSNull || value == nil
+}
+
+private func wrapResult(_ result: Any?) -> [Any?] {
+ return [result]
+}
+
+private func wrapError(_ error: Any) -> [Any?] {
+ if let flutterError = error as? FlutterError {
+ return [
+ flutterError.code,
+ flutterError.message,
+ flutterError.details,
+ ]
+ }
+ return [
+ "\(error)",
+ "\(type(of: error))",
+ "Stacktrace: \(Thread.callStackSymbols)",
+ ]
+}
+
+private func nilOrValue<T>(_ value: Any?) -> T? {
+ if value is NSNull { return nil }
+ return value as! T?
+}
+
+/// Possible outcomes of launching a URL.
+enum LaunchResult: Int {
+ /// The URL was successfully launched (or could be, for `canLaunchUrl`).
+ case success = 0
+ /// There was no handler available for the URL.
+ case failure = 1
+ /// The URL could not be launched because it is invalid.
+ case invalidUrl = 2
+}
+
+/// Possible outcomes of handling a URL within the application.
+enum InAppLoadResult: Int {
+ /// The URL was successfully loaded.
+ case success = 0
+ /// The URL did not load successfully.
+ case failedToLoad = 1
+ /// The URL could not be launched because it is invalid.
+ case invalidUrl = 2
+}
+
+/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
+protocol UrlLauncherApi {
+ /// Checks whether a URL can be loaded.
+ func canLaunchUrl(url: String) throws -> LaunchResult
+ /// Opens the URL externally, returning the status of launching it.
+ func launchUrl(
+ url: String, universalLinksOnly: Bool,
+ completion: @escaping (Result<LaunchResult, Error>) -> Void)
+ /// Opens the URL in an in-app SFSafariViewController, returning the results
+ /// of loading it.
+ func openUrlInSafariViewController(
+ url: String, completion: @escaping (Result<InAppLoadResult, Error>) -> Void)
+ /// Closes the view controller opened by [openUrlInSafariViewController].
+ func closeSafariViewController() throws
+}
+
+/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
+class UrlLauncherApiSetup {
+ /// The codec used by UrlLauncherApi.
+ /// Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`.
+ static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UrlLauncherApi?) {
+ /// Checks whether a URL can be loaded.
+ let canLaunchUrlChannel = FlutterBasicMessageChannel(
+ name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl",
+ binaryMessenger: binaryMessenger)
+ if let api = api {
+ canLaunchUrlChannel.setMessageHandler { message, reply in
+ let args = message as! [Any?]
+ let urlArg = args[0] as! String
+ do {
+ let result = try api.canLaunchUrl(url: urlArg)
+ reply(wrapResult(result.rawValue))
+ } catch {
+ reply(wrapError(error))
+ }
+ }
+ } else {
+ canLaunchUrlChannel.setMessageHandler(nil)
+ }
+ /// Opens the URL externally, returning the status of launching it.
+ let launchUrlChannel = FlutterBasicMessageChannel(
+ name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.launchUrl",
+ binaryMessenger: binaryMessenger)
+ if let api = api {
+ launchUrlChannel.setMessageHandler { message, reply in
+ let args = message as! [Any?]
+ let urlArg = args[0] as! String
+ let universalLinksOnlyArg = args[1] as! Bool
+ api.launchUrl(url: urlArg, universalLinksOnly: universalLinksOnlyArg) { result in
+ switch result {
+ case .success(let res):
+ reply(wrapResult(res.rawValue))
+ case .failure(let error):
+ reply(wrapError(error))
+ }
+ }
+ }
+ } else {
+ launchUrlChannel.setMessageHandler(nil)
+ }
+ /// Opens the URL in an in-app SFSafariViewController, returning the results
+ /// of loading it.
+ let openUrlInSafariViewControllerChannel = FlutterBasicMessageChannel(
+ name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.openUrlInSafariViewController",
+ binaryMessenger: binaryMessenger)
+ if let api = api {
+ openUrlInSafariViewControllerChannel.setMessageHandler { message, reply in
+ let args = message as! [Any?]
+ let urlArg = args[0] as! String
+ api.openUrlInSafariViewController(url: urlArg) { result in
+ switch result {
+ case .success(let res):
+ reply(wrapResult(res.rawValue))
+ case .failure(let error):
+ reply(wrapError(error))
+ }
+ }
+ }
+ } else {
+ openUrlInSafariViewControllerChannel.setMessageHandler(nil)
+ }
+ /// Closes the view controller opened by [openUrlInSafariViewController].
+ let closeSafariViewControllerChannel = FlutterBasicMessageChannel(
+ name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController",
+ binaryMessenger: binaryMessenger)
+ if let api = api {
+ closeSafariViewControllerChannel.setMessageHandler { _, reply in
+ do {
+ try api.closeSafariViewController()
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
+ }
+ } else {
+ closeSafariViewControllerChannel.setMessageHandler(nil)
+ }
+ }
+}
diff --git a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec
index 3dd3eb9..400ad73 100644
--- a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec
+++ b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec
@@ -14,7 +14,7 @@
s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios' }
s.documentation_url = 'https://pub.dev/packages/url_launcher'
s.swift_version = '5.0'
- s.source_files = 'Classes/**/*'
+ s.source_files = 'Classes/**/*.swift'
s.xcconfig = {
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
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
index 562a408..a7e9a8c 100644
--- a/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart
+++ b/packages/url_launcher/url_launcher_ios/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
// 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.2.4), do not edit directly.
+// Autogenerated from Pigeon (v11.0.1), 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
@@ -11,6 +11,30 @@
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
+/// Possible outcomes of launching a URL.
+enum LaunchResult {
+ /// The URL was successfully launched (or could be, for `canLaunchUrl`).
+ success,
+
+ /// There was no handler available for the URL.
+ failure,
+
+ /// The URL could not be launched because it is invalid.
+ invalidUrl,
+}
+
+/// Possible outcomes of handling a URL within the application.
+enum InAppLoadResult {
+ /// The URL was successfully loaded.
+ success,
+
+ /// The URL did not load successfully.
+ failedToLoad,
+
+ /// The URL could not be launched because it is invalid.
+ invalidUrl,
+}
+
class UrlLauncherApi {
/// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
@@ -21,67 +45,10 @@
static const MessageCodec<Object?> codec = StandardMessageCodec();
- /// Returns true if the URL can definitely be launched.
- Future<bool> canLaunchUrl(String arg_url) async {
+ /// Checks whether a URL can be loaded.
+ Future<LaunchResult> 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',
+ 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
@@ -103,14 +70,74 @@
message: 'Host platform returned null value for non-null return value.',
);
} else {
- return (replyList[0] as bool?)!;
+ return LaunchResult.values[replyList[0]! as int];
+ }
+ }
+
+ /// Opens the URL externally, returning the status of launching it.
+ Future<LaunchResult> launchUrl(
+ String arg_url, bool arg_universalLinksOnly) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.url_launcher_ios.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 LaunchResult.values[replyList[0]! as int];
+ }
+ }
+
+ /// Opens the URL in an in-app SFSafariViewController, returning the results
+ /// of loading it.
+ Future<InAppLoadResult> openUrlInSafariViewController(String arg_url) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.url_launcher_ios.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 InAppLoadResult.values[replyList[0]! as int];
}
}
/// Closes the view controller opened by [openUrlInSafariViewController].
Future<void> closeSafariViewController() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UrlLauncherApi.closeSafariViewController', codec,
+ 'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController',
+ codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
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 6696978..9d1ebc9 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
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/foundation.dart' show visibleForTesting;
+import 'package:flutter/services.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
@@ -26,8 +27,9 @@
final LinkDelegate? linkDelegate = null;
@override
- Future<bool> canLaunch(String url) {
- return _hostApi.canLaunchUrl(url);
+ Future<bool> canLaunch(String url) async {
+ final LaunchResult result = await _hostApi.canLaunchUrl(url);
+ return _mapLaunchResult(result);
}
@override
@@ -90,10 +92,12 @@
}
if (inApp) {
- return _hostApi.openUrlInSafariViewController(url);
+ return _mapInAppLoadResult(
+ await _hostApi.openUrlInSafariViewController(url),
+ url: url);
} else {
- return _hostApi.launchUrl(url,
- options.mode == PreferredLaunchMode.externalNonBrowserApplication);
+ return _mapLaunchResult(await _hostApi.launchUrl(url,
+ options.mode == PreferredLaunchMode.externalNonBrowserApplication));
}
}
@@ -120,4 +124,52 @@
return mode == PreferredLaunchMode.inAppWebView ||
mode == PreferredLaunchMode.inAppBrowserView;
}
+
+ bool _mapLaunchResult(LaunchResult result) {
+ switch (result) {
+ case LaunchResult.success:
+ return true;
+ case LaunchResult.failure:
+ return false;
+ case LaunchResult.invalidUrl:
+ throw _invalidUrlException();
+ }
+ }
+
+ bool _mapInAppLoadResult(InAppLoadResult result, {required String url}) {
+ switch (result) {
+ case InAppLoadResult.success:
+ return true;
+ case InAppLoadResult.failedToLoad:
+ throw _failedSafariViewControllerLoadException(url);
+ case InAppLoadResult.invalidUrl:
+ throw _invalidUrlException();
+ }
+ }
+
+ // TODO(stuartmorgan): Remove this as part of standardizing error handling.
+ // See https://github.com/flutter/flutter/issues/127665
+ //
+ // This PlatformException (including the exact string details, since those
+ // are a defacto part of the API) is for compatibility with the previous
+ // native implementation.
+ PlatformException _invalidUrlException() {
+ throw PlatformException(
+ code: 'argument_error',
+ message: 'Unable to parse URL',
+ );
+ }
+
+ // TODO(stuartmorgan): Remove this as part of standardizing error handling.
+ // See https://github.com/flutter/flutter/issues/127665
+ //
+ // This PlatformException (including the exact string details, since those
+ // are a defacto part of the API) is for compatibility with the previous
+ // native implementation.
+ PlatformException _failedSafariViewControllerLoadException(String url) {
+ throw PlatformException(
+ code: 'Error',
+ message: 'Error while launching $url',
+ );
+ }
}
diff --git a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart
index f6935cb..f5dc105 100644
--- a/packages/url_launcher/url_launcher_ios/pigeons/messages.dart
+++ b/packages/url_launcher/url_launcher_ios/pigeons/messages.dart
@@ -6,27 +6,50 @@
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
- objcOptions: ObjcOptions(prefix: 'FUL'),
- objcHeaderOut: 'ios/Classes/messages.g.h',
- objcSourceOut: 'ios/Classes/messages.g.m',
+ swiftOut: 'ios/Classes/messages.g.swift',
copyrightHeader: 'pigeons/copyright.txt',
))
+
+/// Possible outcomes of launching a URL.
+enum LaunchResult {
+ /// The URL was successfully launched (or could be, for `canLaunchUrl`).
+ success,
+
+ /// There was no handler available for the URL.
+ failure,
+
+ /// The URL could not be launched because it is invalid.
+ invalidUrl,
+}
+
+/// Possible outcomes of handling a URL within the application.
+enum InAppLoadResult {
+ /// The URL was successfully loaded.
+ success,
+
+ /// The URL did not load successfully.
+ failedToLoad,
+
+ /// The URL could not be launched because it is invalid.
+ invalidUrl,
+}
+
@HostApi()
abstract class UrlLauncherApi {
- /// Returns true if the URL can definitely be launched.
+ /// Checks whether a URL can be loaded.
@ObjCSelector('canLaunchURL:')
- bool canLaunchUrl(String url);
+ LaunchResult canLaunchUrl(String url);
- /// Opens the URL externally, returning true if successful.
+ /// Opens the URL externally, returning the status of launching it.
@async
@ObjCSelector('launchURL:universalLinksOnly:')
- bool launchUrl(String url, bool universalLinksOnly);
+ LaunchResult launchUrl(String url, bool universalLinksOnly);
- /// Opens the URL in an in-app SFSafariViewController, returning true
- /// when it has loaded successfully.
+ /// Opens the URL in an in-app SFSafariViewController, returning the results
+ /// of loading it.
@async
@ObjCSelector('openSafariViewControllerWithURL:')
- bool openUrlInSafariViewController(String url);
+ InAppLoadResult 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 6047568..56b337b 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.2.0
+version: 6.2.1
environment:
sdk: ">=2.19.0 <4.0.0"
@@ -13,7 +13,7 @@
implements: url_launcher
platforms:
ios:
- pluginClass: FLTURLLauncherPlugin
+ pluginClass: URLLauncherPlugin
dartPluginClass: UrlLauncherIOS
dependencies:
@@ -22,9 +22,11 @@
url_launcher_platform_interface: ^2.2.0
dev_dependencies:
+ build_runner: ^2.3.3
flutter_test:
sdk: flutter
- pigeon: ^9.2.4
+ mockito: 5.4.2
+ pigeon: ^11.0.1
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 bacea31..195db63 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,17 +4,23 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.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();
+import 'url_launcher_ios_test.mocks.dart';
- late _FakeUrlLauncherApi api;
+// A web URL to use in tests where the specifics of the URL don't matter.
+const String _webUrl = 'https://example.com/';
+
+@GenerateMocks(<Type>[UrlLauncherApi])
+void main() {
+ late MockUrlLauncherApi api;
setUp(() {
- api = _FakeUrlLauncherApi();
+ api = MockUrlLauncherApi();
});
test('registers instance', () {
@@ -24,28 +30,38 @@
group('canLaunch', () {
test('handles success', () async {
+ when(api.canLaunchUrl(_webUrl))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- expect(await launcher.canLaunch('http://example.com/'), true);
+ expect(await launcher.canLaunch(_webUrl), true);
});
test('handles failure', () async {
+ when(api.canLaunchUrl(_webUrl))
+ .thenAnswer((_) async => LaunchResult.failure);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- expect(await launcher.canLaunch('unknown://scheme'), false);
+ expect(await launcher.canLaunch(_webUrl), false);
});
- test('passes invalid URL PlatformException through', () async {
+ test('throws PlatformException for invalid URL', () async {
+ when(api.canLaunchUrl(_webUrl))
+ .thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- await expectLater(launcher.canLaunch('invalid://u r l'),
- throwsA(isA<PlatformException>()));
+ await expectLater(
+ launcher.canLaunch(_webUrl),
+ throwsA(isA<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'argument_error')));
});
});
group('legacy launch', () {
test('handles success', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
- 'http://example.com/',
+ _webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -54,14 +70,16 @@
headers: const <String, String>{},
),
true);
- expect(api.passedUniversalLinksOnly, false);
+ verifyNever(api.openUrlInSafariViewController(any));
});
test('handles failure', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.failure);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
- 'unknown://scheme',
+ _webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -70,14 +88,16 @@
headers: const <String, String>{},
),
false);
- expect(api.passedUniversalLinksOnly, false);
+ verifyNever(api.openUrlInSafariViewController(any));
});
- test('passes invalid URL PlatformException through', () async {
+ test('throws PlatformException for invalid URL', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launch(
- 'invalid://u r l',
+ _webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -85,14 +105,17 @@
universalLinksOnly: false,
headers: const <String, String>{},
),
- throwsA(isA<PlatformException>()));
+ throwsA(isA<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'argument_error')));
});
test('force SafariVC is handled', () async {
+ when(api.openUrlInSafariViewController(_webUrl))
+ .thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
- 'http://example.com/',
+ _webUrl,
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
@@ -101,14 +124,16 @@
headers: const <String, String>{},
),
true);
- expect(api.usedSafariViewController, true);
+ verifyNever(api.launchUrl(any, any));
});
test('universal links only is handled', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
- 'http://example.com/',
+ _webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -117,14 +142,16 @@
headers: const <String, String>{},
),
true);
- expect(api.passedUniversalLinksOnly, true);
+ verifyNever(api.openUrlInSafariViewController(any));
});
test('disallowing SafariVC is handled', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
- 'http://example.com/',
+ _webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -133,109 +160,147 @@
headers: const <String, String>{},
),
true);
- expect(api.usedSafariViewController, false);
+ verifyNever(api.openUrlInSafariViewController(any));
});
});
test('closeWebView calls through', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await launcher.closeWebView();
- expect(api.closed, true);
+ verify(api.closeSafariViewController()).called(1);
});
group('launch without webview', () {
test('calls through', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(
- 'http://example.com/',
+ _webUrl,
const LaunchOptions(mode: PreferredLaunchMode.externalApplication),
);
expect(launched, true);
- expect(api.usedSafariViewController, false);
+ verifyNever(api.openUrlInSafariViewController(any));
});
- test('passes invalid URL PlatformException through', () async {
+ test('throws PlatformException for invalid URL', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
- launcher.launchUrl('invalid://u r l', const LaunchOptions()),
- throwsA(isA<PlatformException>()));
+ launcher.launchUrl(
+ _webUrl,
+ const LaunchOptions(mode: PreferredLaunchMode.externalApplication),
+ ),
+ throwsA(isA<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'argument_error')));
});
});
group('launch with Safari view controller', () {
test('calls through with inAppWebView', () async {
+ when(api.openUrlInSafariViewController(_webUrl))
+ .thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- final bool launched = await launcher.launchUrl('http://example.com/',
- const LaunchOptions(mode: PreferredLaunchMode.inAppWebView));
+ final bool launched = await launcher.launchUrl(
+ _webUrl, const LaunchOptions(mode: PreferredLaunchMode.inAppWebView));
expect(launched, true);
- expect(api.usedSafariViewController, true);
+ verifyNever(api.launchUrl(any, any));
});
test('calls through with inAppBrowserView', () async {
+ when(api.openUrlInSafariViewController(_webUrl))
+ .thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- final bool launched = await launcher.launchUrl('http://example.com/',
+ final bool launched = await launcher.launchUrl(_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView));
expect(launched, true);
- expect(api.usedSafariViewController, true);
+ verifyNever(api.launchUrl(any, any));
});
- test('passes invalid URL PlatformException through', () async {
+ test('throws PlatformException for invalid URL', () async {
+ when(api.openUrlInSafariViewController(_webUrl))
+ .thenAnswer((_) async => InAppLoadResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
- launcher.launchUrl('invalid://u r l',
+ launcher.launchUrl(_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)),
- throwsA(isA<PlatformException>()));
+ throwsA(isA<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'argument_error')));
+ });
+
+ test('throws PlatformException for load failure', () async {
+ when(api.openUrlInSafariViewController(_webUrl))
+ .thenAnswer((_) async => InAppLoadResult.failedToLoad);
+ final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
+ await expectLater(
+ launcher.launchUrl(_webUrl,
+ const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)),
+ throwsA(isA<PlatformException>()
+ .having((PlatformException e) => e.code, 'code', 'Error')));
});
});
group('launch with universal links', () {
test('calls through', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(
- 'http://example.com/',
+ _webUrl,
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication),
);
expect(launched, true);
- expect(api.usedSafariViewController, false);
- expect(api.passedUniversalLinksOnly, true);
+ verifyNever(api.openUrlInSafariViewController(any));
});
- test('passes invalid URL PlatformException through', () async {
+ test('throws PlatformException for invalid URL', () async {
+ when(api.launchUrl(_webUrl, any))
+ .thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launchUrl(
- 'invalid://u r l',
+ _webUrl,
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication)),
- throwsA(isA<PlatformException>()));
+ throwsA(isA<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'argument_error')));
});
});
group('launch with platform default', () {
test('uses Safari view controller for http', () async {
+ const String httpUrl = 'http://example.com/';
+ when(api.openUrlInSafariViewController(httpUrl))
+ .thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- final bool launched = await launcher.launchUrl(
- 'http://example.com/', const LaunchOptions());
+ final bool launched =
+ await launcher.launchUrl(httpUrl, const LaunchOptions());
expect(launched, true);
- expect(api.usedSafariViewController, true);
+ verifyNever(api.launchUrl(any, any));
});
test('uses Safari view controller for https', () async {
+ const String httpsUrl = 'https://example.com/';
+ when(api.openUrlInSafariViewController(httpsUrl))
+ .thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- final bool launched = await launcher.launchUrl(
- 'https://example.com/', const LaunchOptions());
+ final bool launched =
+ await launcher.launchUrl(httpsUrl, const LaunchOptions());
expect(launched, true);
- expect(api.usedSafariViewController, true);
+ verifyNever(api.launchUrl(any, any));
});
test('uses standard external for other schemes', () async {
+ const String nonWebUrl = 'supportedcustomscheme://example.com/';
+ when(api.launchUrl(nonWebUrl, any))
+ .thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
- final bool launched = await launcher.launchUrl(
- 'supportedcustomscheme://example.com/', const LaunchOptions());
+ final bool launched =
+ await launcher.launchUrl(nonWebUrl, const LaunchOptions());
expect(launched, true);
- expect(api.usedSafariViewController, false);
- expect(api.passedUniversalLinksOnly, false);
+ verifyNever(api.openUrlInSafariViewController(any));
});
});
@@ -303,49 +368,3 @@
});
});
}
-
-/// A fake implementation of the host API that reacts to specific schemes.
-///
-/// 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':
- case 'supportedcustomscheme':
- return true;
- case 'invalid':
- throw PlatformException(code: 'argument_error');
- default:
- return false;
- }
- }
-}
diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart
new file mode 100644
index 0000000..e9eccab
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.mocks.dart
@@ -0,0 +1,79 @@
+// Mocks generated by Mockito 5.4.2 from annotations
+// in url_launcher_ios/test/url_launcher_ios_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:url_launcher_ios/src/messages.g.dart' as _i2;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+/// A class which mocks [UrlLauncherApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockUrlLauncherApi extends _i1.Mock implements _i2.UrlLauncherApi {
+ MockUrlLauncherApi() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.Future<_i2.LaunchResult> canLaunchUrl(String? arg_url) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #canLaunchUrl,
+ [arg_url],
+ ),
+ returnValue:
+ _i3.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success),
+ ) as _i3.Future<_i2.LaunchResult>);
+
+ @override
+ _i3.Future<_i2.LaunchResult> launchUrl(
+ String? arg_url,
+ bool? arg_universalLinksOnly,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #launchUrl,
+ [
+ arg_url,
+ arg_universalLinksOnly,
+ ],
+ ),
+ returnValue:
+ _i3.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success),
+ ) as _i3.Future<_i2.LaunchResult>);
+
+ @override
+ _i3.Future<_i2.InAppLoadResult> openUrlInSafariViewController(
+ String? arg_url) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #openUrlInSafariViewController,
+ [arg_url],
+ ),
+ returnValue:
+ _i3.Future<_i2.InAppLoadResult>.value(_i2.InAppLoadResult.success),
+ ) as _i3.Future<_i2.InAppLoadResult>);
+
+ @override
+ _i3.Future<void> closeSafariViewController() => (super.noSuchMethod(
+ Invocation.method(
+ #closeSafariViewController,
+ [],
+ ),
+ returnValue: _i3.Future<void>.value(),
+ returnValueForMissingStub: _i3.Future<void>.value(),
+ ) as _i3.Future<void>);
+}