-
Notifications
You must be signed in to change notification settings - Fork 6k
Added the ability to set the initial route via launch urls. #21336
Changes from all commits
fad38fd
1fa2fd2
e4d598c
51e56d6
5441cec
fc2023b
43e792f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,21 @@ | |
|
|
||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" | ||
|
|
||
| #include "flutter/fml/logging.h" | ||
| #import "flutter/fml/logging.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" | ||
|
|
||
| static NSString* kUIBackgroundMode = @"UIBackgroundModes"; | ||
| static NSString* kRemoteNotificationCapabitiliy = @"remote-notification"; | ||
| static NSString* kBackgroundFetchCapatibility = @"fetch"; | ||
|
|
||
| @interface FlutterAppDelegate () | ||
| @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void); | ||
| @end | ||
|
|
||
| @implementation FlutterAppDelegate { | ||
| FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate; | ||
| } | ||
|
|
@@ -26,6 +32,7 @@ - (instancetype)init { | |
|
|
||
| - (void)dealloc { | ||
| [_lifeCycleDelegate release]; | ||
| [_rootFlutterViewControllerGetter release]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class does not create the getter, it seems weird that this class is responsible for destroying it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It actually does create it in its declaration of the property. This is related to your other comment, too. The file FlutterAppDelegate_Test is a thing called a category which allows you to extend classes or make methods selectively visible. You can think of this as declaring that property as |
||
| [super dealloc]; | ||
| } | ||
|
|
||
|
|
@@ -41,10 +48,13 @@ - (BOOL)application:(UIApplication*)application | |
|
|
||
| // Returns the key window's rootViewController, if it's a FlutterViewController. | ||
| // Otherwise, returns nil. | ||
| + (FlutterViewController*)rootFlutterViewController { | ||
| UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; | ||
| if ([viewController isKindOfClass:[FlutterViewController class]]) { | ||
| return (FlutterViewController*)viewController; | ||
| - (FlutterViewController*)rootFlutterViewController { | ||
| if (_rootFlutterViewControllerGetter != nil) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this only used in test? It seems weird that we inject the getter this way, is it possible for the test to override this class and override this method to inject the getter there?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could, but it's the class I'm testing. In order to do that I'd have to make a partial mock of the class I actually want to test which is weird, too. I could do more traditional dependency injection and pass in an object that can get the root FlutterViewController in with the FlutterAppDelegate's initializer. This is basically the same thing but with a setter. I wanted to introduce the least amount of overhead possible, that's why it's a pointer check that will basically be ignored with branch prediction. If I always passed the logic through a block or a protocol we would always incur a dynamic dispatch. |
||
| return _rootFlutterViewControllerGetter(); | ||
| } | ||
| UIViewController* rootViewController = _window.rootViewController; | ||
| if ([rootViewController isKindOfClass:[FlutterViewController class]]) { | ||
| return (FlutterViewController*)rootViewController; | ||
| } | ||
| return nil; | ||
| } | ||
|
|
@@ -124,7 +134,28 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center | |
| - (BOOL)application:(UIApplication*)application | ||
| openURL:(NSURL*)url | ||
| options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { | ||
| return [_lifeCycleDelegate application:application openURL:url options:options]; | ||
| if ([_lifeCycleDelegate application:application openURL:url options:options]) { | ||
| return YES; | ||
| } 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:@"pushRoute" | ||
| arguments:url.path]; | ||
| } | ||
| }]; | ||
| return YES; | ||
| } else { | ||
| FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController."; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what situation this can happen?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unlikely but if you edit the storyboard for the iOS project this could happen. Also, if you were doing add to app and you call into a FlutterAppDelegate it could be a problem. |
||
| return NO; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { | ||
|
|
@@ -175,27 +206,25 @@ - (BOOL)application:(UIApplication*)application | |
| #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController | ||
|
|
||
| - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey { | ||
| UIViewController* rootViewController = _window.rootViewController; | ||
| if ([rootViewController isKindOfClass:[FlutterViewController class]]) { | ||
| return | ||
| [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey]; | ||
| FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; | ||
| if (flutterRootViewController) { | ||
| return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey]; | ||
| } | ||
| return nil; | ||
| } | ||
|
|
||
| - (BOOL)hasPlugin:(NSString*)pluginKey { | ||
| UIViewController* rootViewController = _window.rootViewController; | ||
| if ([rootViewController isKindOfClass:[FlutterViewController class]]) { | ||
| return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey]; | ||
| FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; | ||
| if (flutterRootViewController) { | ||
| return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey]; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { | ||
| UIViewController* rootViewController = _window.rootViewController; | ||
| if ([rootViewController isKindOfClass:[FlutterViewController class]]) { | ||
| return [[(FlutterViewController*)rootViewController pluginRegistry] | ||
| valuePublishedByPlugin:pluginKey]; | ||
| FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; | ||
| if (flutterRootViewController) { | ||
| return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey]; | ||
| } | ||
| return nil; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // 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 <OCMock/OCMock.h> | ||
| #import <XCTest/XCTest.h> | ||
|
|
||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h" | ||
|
|
||
| FLUTTER_ASSERT_ARC | ||
|
|
||
| @interface FlutterAppDelegateTest : XCTestCase | ||
| @end | ||
|
|
||
| @implementation FlutterAppDelegateTest | ||
|
|
||
| - (void)testLaunchUrl { | ||
| FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; | ||
| FlutterViewController* viewController = OCMClassMock([FlutterViewController class]); | ||
| FlutterEngine* engine = OCMClassMock([FlutterEngine class]); | ||
| FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]); | ||
| OCMStub([engine navigationChannel]).andReturn(navigationChannel); | ||
| OCMStub([viewController engine]).andReturn(engine); | ||
| OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]); | ||
| appDelegate.rootFlutterViewControllerGetter = ^{ | ||
| return viewController; | ||
| }; | ||
| NSURL* url = [NSURL URLWithString:@"http://example.com"]; | ||
| NSDictionary<UIApplicationOpenURLOptionsKey, id>* options = @{}; | ||
| BOOL result = [appDelegate application:[UIApplication sharedApplication] | ||
| openURL:url | ||
| options:options]; | ||
| XCTAssertTrue(result); | ||
| OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:url.path]); | ||
| } | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // 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. | ||
|
|
||
| @class FlutterViewController; | ||
|
|
||
| @interface FlutterAppDelegate (Test) | ||
| @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void); | ||
| @end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we decide whether we use import vs include? I originally thought we always include c++ file and import objective c file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the file is objective-c or objective-c++ just always use import.