Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 25bb31e

Browse files
authored
Added the ability to set the initial route via launch urls. (#21336)
1 parent 3f05cab commit 25bb31e

File tree

9 files changed

+129
-17
lines changed

9 files changed

+129
-17
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin
936936
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h
937937
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Info.plist
938938
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
939+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm
940+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h
939941
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h
940942
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm
941943
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm

shell/platform/darwin/ios/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ shared_library("ios_test_flutter") {
206206
"//build/config:symbol_visibility_hidden",
207207
]
208208
sources = [
209+
"framework/Source/FlutterAppDelegateTest.mm",
209210
"framework/Source/FlutterBinaryMessengerRelayTest.mm",
210211
"framework/Source/FlutterDartProjectTest.mm",
211212
"framework/Source/FlutterEngineTest.mm",

shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
66

7-
#include "flutter/fml/logging.h"
7+
#import "flutter/fml/logging.h"
88
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
99
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
10+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
11+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
1012
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
1113

1214
static NSString* kUIBackgroundMode = @"UIBackgroundModes";
1315
static NSString* kRemoteNotificationCapabitiliy = @"remote-notification";
1416
static NSString* kBackgroundFetchCapatibility = @"fetch";
1517

18+
@interface FlutterAppDelegate ()
19+
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
20+
@end
21+
1622
@implementation FlutterAppDelegate {
1723
FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
1824
}
@@ -26,6 +32,7 @@ - (instancetype)init {
2632

2733
- (void)dealloc {
2834
[_lifeCycleDelegate release];
35+
[_rootFlutterViewControllerGetter release];
2936
[super dealloc];
3037
}
3138

@@ -41,10 +48,13 @@ - (BOOL)application:(UIApplication*)application
4148

4249
// Returns the key window's rootViewController, if it's a FlutterViewController.
4350
// Otherwise, returns nil.
44-
+ (FlutterViewController*)rootFlutterViewController {
45-
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
46-
if ([viewController isKindOfClass:[FlutterViewController class]]) {
47-
return (FlutterViewController*)viewController;
51+
- (FlutterViewController*)rootFlutterViewController {
52+
if (_rootFlutterViewControllerGetter != nil) {
53+
return _rootFlutterViewControllerGetter();
54+
}
55+
UIViewController* rootViewController = _window.rootViewController;
56+
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
57+
return (FlutterViewController*)rootViewController;
4858
}
4959
return nil;
5060
}
@@ -124,7 +134,28 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center
124134
- (BOOL)application:(UIApplication*)application
125135
openURL:(NSURL*)url
126136
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
127-
return [_lifeCycleDelegate application:application openURL:url options:options];
137+
if ([_lifeCycleDelegate application:application openURL:url options:options]) {
138+
return YES;
139+
} else {
140+
FlutterViewController* flutterViewController = [self rootFlutterViewController];
141+
if (flutterViewController) {
142+
[flutterViewController.engine
143+
waitForFirstFrame:3.0
144+
callback:^(BOOL didTimeout) {
145+
if (didTimeout) {
146+
FML_LOG(ERROR)
147+
<< "Timeout waiting for the first frame when launching an URL.";
148+
} else {
149+
[flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute"
150+
arguments:url.path];
151+
}
152+
}];
153+
return YES;
154+
} else {
155+
FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
156+
return NO;
157+
}
158+
}
128159
}
129160

130161
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
@@ -175,27 +206,25 @@ - (BOOL)application:(UIApplication*)application
175206
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
176207

177208
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
178-
UIViewController* rootViewController = _window.rootViewController;
179-
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
180-
return
181-
[[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey];
209+
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
210+
if (flutterRootViewController) {
211+
return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
182212
}
183213
return nil;
184214
}
185215

186216
- (BOOL)hasPlugin:(NSString*)pluginKey {
187-
UIViewController* rootViewController = _window.rootViewController;
188-
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
189-
return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey];
217+
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
218+
if (flutterRootViewController) {
219+
return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
190220
}
191221
return false;
192222
}
193223

194224
- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
195-
UIViewController* rootViewController = _window.rootViewController;
196-
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
197-
return [[(FlutterViewController*)rootViewController pluginRegistry]
198-
valuePublishedByPlugin:pluginKey];
225+
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
226+
if (flutterRootViewController) {
227+
return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
199228
}
200229
return nil;
201230
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <OCMock/OCMock.h>
6+
#import <XCTest/XCTest.h>
7+
8+
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
9+
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
10+
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
11+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
12+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
13+
14+
FLUTTER_ASSERT_ARC
15+
16+
@interface FlutterAppDelegateTest : XCTestCase
17+
@end
18+
19+
@implementation FlutterAppDelegateTest
20+
21+
- (void)testLaunchUrl {
22+
FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init];
23+
FlutterViewController* viewController = OCMClassMock([FlutterViewController class]);
24+
FlutterEngine* engine = OCMClassMock([FlutterEngine class]);
25+
FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]);
26+
OCMStub([engine navigationChannel]).andReturn(navigationChannel);
27+
OCMStub([viewController engine]).andReturn(engine);
28+
OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]);
29+
appDelegate.rootFlutterViewControllerGetter = ^{
30+
return viewController;
31+
};
32+
NSURL* url = [NSURL URLWithString:@"http://example.com"];
33+
NSDictionary<UIApplicationOpenURLOptionsKey, id>* options = @{};
34+
BOOL result = [appDelegate application:[UIApplication sharedApplication]
35+
openURL:url
36+
options:options];
37+
XCTAssertTrue(result);
38+
OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:url.path]);
39+
}
40+
41+
@end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@class FlutterViewController;
6+
7+
@interface FlutterAppDelegate (Test)
8+
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
9+
@end

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,19 @@ - (void)onLocaleUpdated:(NSNotification*)notification {
883883
[self.localizationChannel invokeMethod:@"setLocale" arguments:localeData];
884884
}
885885

886+
- (void)waitForFirstFrame:(NSTimeInterval)timeout
887+
callback:(void (^_Nonnull)(BOOL didTimeout))callback {
888+
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
889+
dispatch_async(queue, ^{
890+
fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000);
891+
BOOL didTimeout =
892+
self.shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded;
893+
dispatch_async(dispatch_get_main_queue(), ^{
894+
callback(didTimeout);
895+
});
896+
});
897+
}
898+
886899
@end
887900

888901
@implementation FlutterEngineRegistrar {

shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,17 @@ - (void)testRunningInitialRouteSendsNavigationMessage {
100100
message:encodedSetInitialRouteMethod]);
101101
}
102102

103+
- (void)testWaitForFirstFrameTimeout {
104+
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
105+
[engine run];
106+
XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"];
107+
[engine waitForFirstFrame:0.1
108+
callback:^(BOOL didTimeout) {
109+
if (timeoutFirstFrame) {
110+
[timeoutFirstFrame fulfill];
111+
}
112+
}];
113+
[self waitForExpectationsWithTimeout:1 handler:nil];
114+
}
115+
103116
@end

shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
- (void)notifyLowMemory;
4949
- (flutter::PlatformViewIOS*)iosPlatformView;
5050

51+
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;
5152
@end
5253

5354
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERENGINE_INTERNAL_H_

shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
66

7+
@class FlutterBinaryMessengerRelay;
8+
79
// Category to add test-only visibility.
810
@interface FlutterEngine (Test) <FlutterBinaryMessenger>
911
- (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger;
12+
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;
1013
@end

0 commit comments

Comments
 (0)