mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[deep link][ios] Update openURL method to reflect the result from framework (flutter/engine#52643)
follow up on comments on https://github.com/flutter/engine/pull/52350 framework pr : https://github.com/flutter/flutter/pull/147901 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
This commit is contained in:
parent
130975a768
commit
d6cff07930
@ -134,44 +134,47 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)openURL:(NSURL*)url {
|
||||
- (BOOL)isFlutterDeepLinkingEnabled {
|
||||
NSNumber* isDeepLinkingEnabled =
|
||||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"];
|
||||
if (!isDeepLinkingEnabled.boolValue) {
|
||||
// Not set or NO.
|
||||
return NO;
|
||||
} else {
|
||||
FlutterViewController* flutterViewController = [self rootFlutterViewController];
|
||||
if (flutterViewController) {
|
||||
[flutterViewController.engine
|
||||
waitForFirstFrame:3.0
|
||||
callback:^(BOOL didTimeout) {
|
||||
if (didTimeout) {
|
||||
FML_LOG(ERROR)
|
||||
<< "Timeout waiting for the first frame when launching an URL.";
|
||||
} else {
|
||||
[flutterViewController.engine.navigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{
|
||||
@"location" : url.absoluteString ?: [NSNull null],
|
||||
}];
|
||||
}
|
||||
}];
|
||||
return YES;
|
||||
} else {
|
||||
FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
// if not set, return NO
|
||||
return isDeepLinkingEnabled ? [isDeepLinkingEnabled boolValue] : NO;
|
||||
}
|
||||
|
||||
// This method is called when opening an URL with custom schemes.
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
openURL:(NSURL*)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
|
||||
if ([_lifeCycleDelegate application:application openURL:url options:options]) {
|
||||
return YES;
|
||||
}
|
||||
return [self openURL:url];
|
||||
|
||||
// Relaying to the system here will case an infinite loop, so we don't do it here.
|
||||
return [self handleOpenURL:url options:options relayToSystemIfUnhandled:NO];
|
||||
}
|
||||
|
||||
// Helper function for opening an URL, either with a custom scheme or a http/https scheme.
|
||||
- (BOOL)handleOpenURL:(NSURL*)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
|
||||
relayToSystemIfUnhandled:(BOOL)throwBack {
|
||||
if (![self isFlutterDeepLinkingEnabled]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
FlutterViewController* flutterViewController = [self rootFlutterViewController];
|
||||
if (flutterViewController) {
|
||||
[flutterViewController sendDeepLinkToFramework:url
|
||||
completionHandler:^(BOOL success) {
|
||||
if (!success && throwBack) {
|
||||
// throw it back to iOS
|
||||
[UIApplication.sharedApplication openURL:url];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
|
||||
@ -204,6 +207,7 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
|
||||
completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
// This method is called when opening an URL with a http/https scheme.
|
||||
- (BOOL)application:(UIApplication*)application
|
||||
continueUserActivity:(NSUserActivity*)userActivity
|
||||
restorationHandler:
|
||||
@ -214,7 +218,8 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
|
||||
restorationHandler:restorationHandler]) {
|
||||
return YES;
|
||||
}
|
||||
return [self openURL:userActivity.webpageURL];
|
||||
|
||||
return [self handleOpenURL:userActivity.webpageURL options:@{} relayToSystemIfUnhandled:YES];
|
||||
}
|
||||
|
||||
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
|
||||
|
||||
@ -62,14 +62,18 @@ FLUTTER_ASSERT_ARC
|
||||
OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
|
||||
.andReturn(@YES);
|
||||
|
||||
OCMStub([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test"}])
|
||||
.andReturn(@YES);
|
||||
|
||||
BOOL result =
|
||||
[self.appDelegate application:[UIApplication sharedApplication]
|
||||
openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test"]
|
||||
options:@{}];
|
||||
|
||||
XCTAssertTrue(result);
|
||||
OCMVerify([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test"}]);
|
||||
OCMVerifyAll(self.mockNavigationChannel);
|
||||
}
|
||||
|
||||
- (void)testLaunchUrlWithDeepLinkingNotSet {
|
||||
@ -99,29 +103,31 @@ FLUTTER_ASSERT_ARC
|
||||
- (void)testLaunchUrlWithQueryParameterAndFragment {
|
||||
OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
|
||||
.andReturn(@YES);
|
||||
|
||||
OCMStub([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test#fragment"}])
|
||||
.andReturn(@YES);
|
||||
BOOL result = [self.appDelegate
|
||||
application:[UIApplication sharedApplication]
|
||||
openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test#fragment"]
|
||||
options:@{}];
|
||||
XCTAssertTrue(result);
|
||||
OCMVerify([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test#fragment"}]);
|
||||
OCMVerifyAll(self.mockNavigationChannel);
|
||||
}
|
||||
|
||||
- (void)testLaunchUrlWithFragmentNoQueryParameter {
|
||||
OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
|
||||
.andReturn(@YES);
|
||||
|
||||
OCMStub([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route#fragment"}])
|
||||
.andReturn(@YES);
|
||||
BOOL result =
|
||||
[self.appDelegate application:[UIApplication sharedApplication]
|
||||
openURL:[NSURL URLWithString:@"http://myApp/custom/route#fragment"]
|
||||
options:@{}];
|
||||
XCTAssertTrue(result);
|
||||
OCMVerify([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route#fragment"}]);
|
||||
OCMVerifyAll(self.mockNavigationChannel);
|
||||
}
|
||||
|
||||
- (void)testReleasesWindowOnDealloc {
|
||||
@ -145,7 +151,10 @@ FLUTTER_ASSERT_ARC
|
||||
- (void)testUniversalLinkPushRouteInformation {
|
||||
OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
|
||||
.andReturn(@YES);
|
||||
|
||||
OCMStub([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test"}])
|
||||
.andReturn(@YES);
|
||||
NSUserActivity* userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.test"];
|
||||
userActivity.webpageURL = [NSURL URLWithString:@"http://myApp/custom/route?query=test"];
|
||||
BOOL result = [self.appDelegate
|
||||
@ -154,9 +163,7 @@ FLUTTER_ASSERT_ARC
|
||||
restorationHandler:^(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects){
|
||||
}];
|
||||
XCTAssertTrue(result);
|
||||
OCMVerify([self.mockNavigationChannel
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{@"location" : @"http://myApp/custom/route?query=test"}]);
|
||||
OCMVerifyAll(self.mockNavigationChannel);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -1863,6 +1863,33 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
[self.keyboardManager handlePress:press nextAction:next];
|
||||
}
|
||||
|
||||
- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion {
|
||||
[_engine.get()
|
||||
waitForFirstFrame:3.0
|
||||
callback:^(BOOL didTimeout) {
|
||||
if (didTimeout) {
|
||||
FML_LOG(ERROR) << "Timeout waiting for the first frame when launching an URL.";
|
||||
completion(NO);
|
||||
} else {
|
||||
// invove the method and get the result
|
||||
[[_engine.get() navigationChannel]
|
||||
invokeMethod:@"pushRouteInformation"
|
||||
arguments:@{
|
||||
@"location" : url.absoluteString ?: [NSNull null],
|
||||
}
|
||||
result:^(id _Nullable result) {
|
||||
BOOL success =
|
||||
[result isKindOfClass:[NSNumber class]] && [result boolValue];
|
||||
if (!success) {
|
||||
// Logging the error if the result is not successful
|
||||
FML_LOG(ERROR) << "Failed to handle route information in Flutter.";
|
||||
}
|
||||
completion(success);
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// The documentation for presses* handlers (implemented below) is entirely
|
||||
// unclear about how to handle the case where some, but not all, of the presses
|
||||
// are handled here. I've elected to call super separately for each of the
|
||||
|
||||
@ -65,12 +65,12 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint);
|
||||
// handled.
|
||||
- (void)handlePressEvent:(FlutterUIPressProxy*)press
|
||||
nextAction:(void (^)())nextAction API_AVAILABLE(ios(13.4));
|
||||
- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion;
|
||||
- (void)addInternalPlugins;
|
||||
- (void)deregisterNotifications;
|
||||
- (int32_t)accessibilityFlags;
|
||||
|
||||
- (BOOL)supportsShowingSystemContextMenu;
|
||||
|
||||
@end
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user