[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:
hangyu 2024-07-02 11:25:39 -07:00 committed by GitHub
parent 130975a768
commit d6cff07930
4 changed files with 84 additions and 45 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_