From 46070d433ebb6ab0503236c00c441ece973e6da5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 17 Mar 2016 16:42:16 -0700 Subject: [PATCH] Make the iOS shell use the public Flutter framework API. Also cleans up the messages when errors occur in both modes. I have also made the API a lot simpler and moved more of the extraneous stuff out of public. Only FlutterViewController and the Dart project configuration are publically available. --- sky/shell/BUILD.gn | 7 +- sky/shell/platform/ios/FlutterAppDelegate.mm | 35 +++- sky/shell/platform/ios/FlutterDartProject.mm | 169 ++++++++++++++++++ .../ios/FlutterDartProject_Internal.h | 26 +++ sky/shell/platform/ios/FlutterDartSource.h | 26 +++ sky/shell/platform/ios/FlutterDartSource.mm | 73 ++++++++ .../platform/ios/FlutterViewController.mm | 92 +++------- .../platform/ios/public/FlutterDartProject.h | 25 +++ .../ios/public/FlutterViewController.h | 31 +--- 9 files changed, 388 insertions(+), 96 deletions(-) create mode 100644 sky/shell/platform/ios/FlutterDartProject.mm create mode 100644 sky/shell/platform/ios/FlutterDartProject_Internal.h create mode 100644 sky/shell/platform/ios/FlutterDartSource.h create mode 100644 sky/shell/platform/ios/FlutterDartSource.mm create mode 100644 sky/shell/platform/ios/public/FlutterDartProject.h diff --git a/sky/shell/BUILD.gn b/sky/shell/BUILD.gn index c477eaef62d..d553e0d6e8e 100644 --- a/sky/shell/BUILD.gn +++ b/sky/shell/BUILD.gn @@ -244,6 +244,10 @@ if (is_android) { sources = [ "platform/ios/FlutterAppDelegate.h", "platform/ios/FlutterAppDelegate.mm", + "platform/ios/FlutterDartProject.mm", + "platform/ios/FlutterDartProject_Internal.h", + "platform/ios/FlutterDartSource.h", + "platform/ios/FlutterDartSource.mm", "platform/ios/FlutterDynamicServiceLoader.h", "platform/ios/FlutterDynamicServiceLoader.mm", "platform/ios/FlutterView.h", @@ -251,10 +255,9 @@ if (is_android) { "platform/ios/FlutterViewController.mm", "platform/ios/flutter_touch_mapper.h", "platform/ios/flutter_touch_mapper.mm", - "platform/ios/framework/Info.plist", - "platform/ios/framework/module.modulemap", "platform/ios/main_ios.mm", "platform/ios/public/Flutter.h", + "platform/ios/public/FlutterDartProject.h", "platform/ios/public/FlutterMacros.h", "platform/ios/public/FlutterViewController.h", ] diff --git a/sky/shell/platform/ios/FlutterAppDelegate.mm b/sky/shell/platform/ios/FlutterAppDelegate.mm index d099572ef1b..bbf089e9239 100644 --- a/sky/shell/platform/ios/FlutterAppDelegate.mm +++ b/sky/shell/platform/ios/FlutterAppDelegate.mm @@ -2,9 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/command_line.h" +#include "base/trace_event/trace_event.h" #include "sky/shell/platform/ios/FlutterAppDelegate.h" #include "sky/shell/platform/ios/public/FlutterViewController.h" -#include "base/trace_event/trace_event.h" +#include "sky/shell/switches.h" + +NSURL* URLForSwitch(const char* name) { + auto cmd = *base::CommandLine::ForCurrentProcess(); + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + + if (cmd.HasSwitch(name)) { + auto url = [NSURL fileURLWithPath:@(cmd.GetSwitchValueASCII(name).c_str())]; + [defaults setURL:url forKey:@(name)]; + [defaults synchronize]; + return url; + } + + return [defaults URLForKey:@(name)]; +} @implementation FlutterAppDelegate @@ -12,13 +28,24 @@ didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { TRACE_EVENT0("flutter", "applicationDidFinishLaunchingWithOptions"); - NSBundle* dartBundle = [NSBundle - bundleWithIdentifier:@"io.flutter.application.FlutterApplication"]; +#if TARGET_IPHONE_SIMULATOR + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithFLXArchive:URLForSwitch(sky::shell::switches::kFLX) + dartMain:URLForSwitch(sky::shell::switches::kMainDartFile) + packageRoot:URLForSwitch(sky::shell::switches::kPackageRoot)]; +#else + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithPrecompiledDartBundle: + [NSBundle bundleWithIdentifier: + @"io.flutter.application.FlutterApplication"]]; +#endif CGRect frame = [UIScreen mainScreen].bounds; UIWindow* window = [[UIWindow alloc] initWithFrame:frame]; FlutterViewController* viewController = - [[FlutterViewController alloc] initWithDartBundle:dartBundle]; + [[FlutterViewController alloc] initWithProject:project + nibName:nil + bundle:nil]; window.rootViewController = viewController; [viewController release]; self.window = window; diff --git a/sky/shell/platform/ios/FlutterDartProject.mm b/sky/shell/platform/ios/FlutterDartProject.mm new file mode 100644 index 00000000000..9c07ec2bcb2 --- /dev/null +++ b/sky/shell/platform/ios/FlutterDartProject.mm @@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sky/shell/platform/ios/FlutterDartProject_Internal.h" +#include "sky/shell/platform/ios/FlutterDartSource.h" + +@implementation FlutterDartProject { + NSBundle* _precompiledDartBundle; + FlutterDartSource* _dartSource; + + VMType _vmTypeRequirement; +} + +#pragma mark - Override base class designated initializers + +- (instancetype)init { + return [self initWithFLXArchive:nil dartMain:nil packageRoot:nil]; +} + +#pragma mark - Designated initializers + +- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle { + self = [super init]; + + if (self) { + _precompiledDartBundle = [bundle copy]; + + [self checkReadiness]; + } + + return self; +} + +- (instancetype)initWithFLXArchive:(NSURL*)archiveURL + dartMain:(NSURL*)dartMainURL + packageRoot:(NSURL*)dartPackageURL { + self = [super init]; + + if (self) { + _dartSource = [[FlutterDartSource alloc] initWithDartMain:dartMainURL + packageRoot:dartPackageURL + flxArchive:archiveURL]; + + [self checkReadiness]; + } + + return self; +} + +#pragma mark - Common initialization tasks + +- (void)checkReadiness { + if (_precompiledDartBundle != nil) { + _vmTypeRequirement = VMTypePrecompilation; + return; + } + + if (_dartSource != nil) { + _vmTypeRequirement = VMTypeInterpreter; + return; + } +} + +#pragma mark - Launching the project in a preconfigured engine. + +static NSString* NSStringFromVMType(VMType type) { + switch (type) { + case VMTypeInvalid: + return @"Invalid"; + case VMTypeInterpreter: + return @"Interpreter"; + case VMTypePrecompilation: + return @"Precompilation"; + } + + return @"Unknown"; +} + +- (void)launchInEngine:(sky::SkyEnginePtr&)engine + embedderVMType:(VMType)embedderVMType + result:(LaunchResult)result { + if (_vmTypeRequirement == VMTypeInvalid) { + result(NO, @"The Dart project is invalid and cannot be loaded by any VM."); + return; + } + + if (embedderVMType == VMTypeInvalid) { + result(NO, @"The embedder is invalid."); + return; + } + + if (_vmTypeRequirement != embedderVMType) { + NSString* message = [NSString + stringWithFormat: + @"Could not load the project because of differing project type. " + @"The project can run in '%@' but the embedder is configured as " + @"'%@'", + NSStringFromVMType(_vmTypeRequirement), + NSStringFromVMType(embedderVMType)]; + result(NO, message); + return; + } + + switch (_vmTypeRequirement) { + case VMTypeInterpreter: + [self runFromSourceInEngine:engine result:result]; + return; + case VMTypePrecompilation: + [self runFromPrecompiledSourceInEngine:engine result:result]; + break; + case VMTypeInvalid: + break; + } + + return result(NO, @"Internal error"); +} + +#pragma mark - Running from precompiled application bundles + +- (void)runFromPrecompiledSourceInEngine:(sky::SkyEnginePtr&)engine + result:(LaunchResult)result { + NSString* path = + [_precompiledDartBundle pathForResource:@"app" ofType:@"flx"]; + + if (path.length == 0) { + NSString* message = + [NSString stringWithFormat:@"Could not find the 'app.flx' archive in " + @"the precompiled Dart bundle with ID '%@'", + _precompiledDartBundle.bundleIdentifier]; + result(NO, message); + return; + } + + engine->RunFromPrecompiledSnapshot(path.UTF8String); + result(YES, @"Success"); +} + +#pragma mark - Running from source + +- (void)runFromSourceInEngine:(sky::SkyEnginePtr&)engine + result:(LaunchResult)result { + if (_dartSource == nil) { + result(NO, @"Dart source not specified."); + return; + } + + [_dartSource validate:^(BOOL success, NSString* message) { + if (!success) { + return result(NO, message); + } + + engine->RunFromFile(_dartSource.dartMain.absoluteURL.path.UTF8String, + _dartSource.packageRoot.absoluteURL.path.UTF8String, + _dartSource.flxArchive.absoluteURL.path.UTF8String); + result(YES, @"Success"); + }]; +} + +#pragma mark - Misc. + +- (void)dealloc { + [_precompiledDartBundle release]; + [_dartSource release]; + + [super dealloc]; +} + +@end diff --git a/sky/shell/platform/ios/FlutterDartProject_Internal.h b/sky/shell/platform/ios/FlutterDartProject_Internal.h new file mode 100644 index 00000000000..deae6f06e35 --- /dev/null +++ b/sky/shell/platform/ios/FlutterDartProject_Internal.h @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sky/shell/platform/ios/public/FlutterDartProject.h" + +#include "sky/services/engine/sky_engine.mojom.h" + +enum VMType { + // An invalid VM configuration. + VMTypeInvalid = 0, + // VM can execute Dart code as an interpreter. + VMTypeInterpreter, + // VM can execute precompiled Dart code. + VMTypePrecompilation, +}; + +typedef void (^LaunchResult)(BOOL success, NSString* message); + +@interface FlutterDartProject () + +- (void)launchInEngine:(sky::SkyEnginePtr&)engine + embedderVMType:(VMType)type + result:(LaunchResult)result; + +@end diff --git a/sky/shell/platform/ios/FlutterDartSource.h b/sky/shell/platform/ios/FlutterDartSource.h new file mode 100644 index 00000000000..72391fea383 --- /dev/null +++ b/sky/shell/platform/ios/FlutterDartSource.h @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SKY_SHELL_PLATFORM_IOS_FLUTTERDARTSOURCE_H_ +#define SKY_SHELL_PLATFORM_IOS_FLUTTERDARTSOURCE_H_ + +#import + +typedef void (^ValidationResult)(BOOL result, NSString* message); + +@interface FlutterDartSource : NSObject + +@property(nonatomic, readonly) NSURL* dartMain; +@property(nonatomic, readonly) NSURL* packageRoot; +@property(nonatomic, readonly) NSURL* flxArchive; + +- (instancetype)initWithDartMain:(NSURL*)dartMain + packageRoot:(NSURL*)packageRoot + flxArchive:(NSURL*)flxArchive NS_DESIGNATED_INITIALIZER; + +- (void)validate:(ValidationResult)result; + +@end + +#endif // SKY_SHELL_PLATFORM_IOS_FLUTTERDARTSOURCE_H_ diff --git a/sky/shell/platform/ios/FlutterDartSource.mm b/sky/shell/platform/ios/FlutterDartSource.mm new file mode 100644 index 00000000000..2b9ec5f2837 --- /dev/null +++ b/sky/shell/platform/ios/FlutterDartSource.mm @@ -0,0 +1,73 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sky/shell/platform/ios/FlutterDartSource.h" + +@implementation FlutterDartSource { + NSURL* _dartMain; + NSURL* _packageRoot; + NSURL* _flxArchive; +} + +- (instancetype)init { + return [self initWithDartMain:nil packageRoot:nil flxArchive:nil]; +} + +- (instancetype)initWithDartMain:(NSURL*)dartMain + packageRoot:(NSURL*)packageRoot + flxArchive:(NSURL*)flxArchive { + self = [super init]; + + if (self) { + _dartMain = [dartMain copy]; + _packageRoot = [packageRoot copy]; + _flxArchive = [flxArchive copy]; + } + + return self; +} + +static BOOL CheckDartProjectURL(NSMutableString* log, + NSURL* url, + NSString* logLabel) { + if (url == nil) { + [log appendFormat:@"The %@ was not specified.\n", logLabel]; + return false; + } + + if (!url.isFileURL) { + [log appendFormat:@"The %@ must be a file URL.\n", logLabel]; + return false; + } + + if (![[NSFileManager defaultManager] fileExistsAtPath:url.absoluteURL.path]) { + [log appendFormat:@"No file found at '%@' when looking for the %@.\n", url, + logLabel]; + return false; + } + + return true; +} + +- (void)validate:(ValidationResult)result { + NSMutableString* log = [[[NSMutableString alloc] init] autorelease]; + + BOOL isValid = YES; + + isValid &= CheckDartProjectURL(log, _flxArchive, @"FLX archive"); + isValid &= CheckDartProjectURL(log, _dartMain, @"Dart main"); + isValid &= CheckDartProjectURL(log, _packageRoot, @"Dart package root"); + + result(isValid, log); +} + +- (void)dealloc { + [_dartMain release]; + [_packageRoot release]; + [_flxArchive release]; + + [super dealloc]; +} + +@end diff --git a/sky/shell/platform/ios/FlutterViewController.mm b/sky/shell/platform/ios/FlutterViewController.mm index 22ec377cd6c..a075ddf3315 100644 --- a/sky/shell/platform/ios/FlutterViewController.mm +++ b/sky/shell/platform/ios/FlutterViewController.mm @@ -11,6 +11,7 @@ #include "sky/services/engine/sky_engine.mojom.h" #include "sky/services/platform/ios/system_chrome_impl.h" #include "sky/shell/platform/ios/flutter_touch_mapper.h" +#include "sky/shell/platform/ios/FlutterDartProject_Internal.h" #include "sky/shell/platform/ios/FlutterDynamicServiceLoader.h" #include "sky/shell/platform/ios/FlutterView.h" #include "sky/shell/platform/mac/platform_mac.h" @@ -20,7 +21,7 @@ #include "sky/shell/shell_view.h" @implementation FlutterViewController { - NSBundle* _dartBundle; + FlutterDartProject* _dartProject; UIInterfaceOrientationMask _orientationPreferences; FlutterDynamicServiceLoader* _dynamicServiceLoader; sky::ViewportMetricsPtr _viewportMetrics; @@ -32,13 +33,13 @@ #pragma mark - Manage and override all designated initializers -- (instancetype)initWithDartBundle:(NSBundle*)dartBundleOrNil - nibName:(NSString*)nibNameOrNil - bundle:(NSBundle*)bundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:bundleOrNil]; +- (instancetype)initWithProject:(FlutterDartProject*)project + nibName:(NSString*)nibNameOrNil + bundle:(NSBundle*)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { - _dartBundle = [dartBundleOrNil retain]; + _dartProject = [project retain]; [self performCommonViewControllerInitialization]; } @@ -48,17 +49,11 @@ - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil { - return [self initWithDartBundle:nil nibName:nil bundle:nil]; + return [self initWithProject:nil nibName:nil bundle:nil]; } - (instancetype)initWithCoder:(NSCoder*)aDecoder { - return [self initWithDartBundle:nil nibName:nil bundle:nil]; -} - -#pragma mark - Implement convenience initializers - -- (instancetype)initWithDartBundle:(NSBundle*)dartBundle { - return [self initWithDartBundle:dartBundle nibName:nil bundle:nil]; + return [self initWithProject:nil nibName:nil bundle:nil]; } #pragma mark - Common view controller initialization tasks @@ -140,11 +135,28 @@ [self setupPlatformServiceProvider]; + enum VMType type = VMTypeInvalid; + #if TARGET_IPHONE_SIMULATOR - [self runFromDartSource]; + type = VMTypeInterpreter; #else - [self runFromPrecompiledSource]; + type = VMTypePrecompilation; #endif + + [_dartProject launchInEngine:_engine + embedderVMType:type + result:^(BOOL success, NSString* message) { + if (!success) { + UIAlertView* alert = [[UIAlertView alloc] + initWithTitle:@"Launch Error" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + }]; } static void DynamicServiceResolve(void* baton, @@ -171,52 +183,6 @@ static void DynamicServiceResolve(void* baton, _engine->SetServices(services.Pass()); } -#if TARGET_IPHONE_SIMULATOR - -- (void)runFromDartSource { - if (sky::shell::AttemptLaunchFromCommandLineSwitches(_engine)) { - return; - } - - UIAlertView* alert = [[UIAlertView alloc] - initWithTitle:@"Error" - message:@"Could not resolve one or all of either the main dart " - @"file path, the FLX bundle path or the package root " - @"on the host. Use the tooling to relaunch the " - @"application." - delegate:self - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; - [alert release]; -} - -#else - -- (void)runFromPrecompiledSource { - mojo::String bundle_path([self flxBundlePath]); - CHECK(bundle_path.size() != 0) - << "There must be a valid FLX bundle to run the application"; - _engine->RunFromPrecompiledSnapshot(bundle_path); -} - -- (const char*)flxBundlePath { - // In case this runner is part of the precompilation SDK, the FLX bundle is - // present in the application bundle instead of the runner bundle. Attempt - // to resolve the path there first. - - NSString* path = [_dartBundle pathForResource:@"app" ofType:@"flx"]; - - if (path.length != 0) { - return path.UTF8String; - } - - return - [[NSBundle mainBundle] pathForResource:@"app" ofType:@"flx"].UTF8String; -} - -#endif // TARGET_IPHONE_SIMULATOR - #pragma mark - Loading the view - (void)loadView { @@ -459,7 +425,7 @@ static inline PointerTypeMapperPhase PointerTypePhaseFromUITouchPhase( [[NSNotificationCenter defaultCenter] removeObserver:self]; [_dynamicServiceLoader release]; - [_dartBundle release]; + [_dartProject release]; [super dealloc]; } diff --git a/sky/shell/platform/ios/public/FlutterDartProject.h b/sky/shell/platform/ios/public/FlutterDartProject.h new file mode 100644 index 00000000000..5f182031b90 --- /dev/null +++ b/sky/shell/platform/ios/public/FlutterDartProject.h @@ -0,0 +1,25 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLUTTERDARTPROJECT_H_ +#define FLUTTER_FLUTTERDARTPROJECT_H_ + +#import + +#include "FlutterMacros.h" + +FLUTTER_EXPORT +@interface FlutterDartProject : NSObject + +- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithFLXArchive:(NSURL*)archiveURL + dartMain:(NSURL*)dartMainURL + packageRoot:(NSURL*)dartPackageURL + NS_DESIGNATED_INITIALIZER; + +@end + +#endif // FLUTTER_FLUTTERDARTPROJECT_H_ diff --git a/sky/shell/platform/ios/public/FlutterViewController.h b/sky/shell/platform/ios/public/FlutterViewController.h index d34c9119177..7e5f60cc90f 100644 --- a/sky/shell/platform/ios/public/FlutterViewController.h +++ b/sky/shell/platform/ios/public/FlutterViewController.h @@ -6,39 +6,16 @@ #define FLUTTER_FLUTTERVIEWCONTROLLER_H_ #include "FlutterMacros.h" +#include "FlutterDartProject.h" #import FLUTTER_EXPORT @interface FlutterViewController : UIViewController -/** - * Initialize the view controller using the specified framework bundle - * containing the precompiled Dart code. - * - * @param dartBundle the framework bundle containing the precompiled Dart code. - * - * @return the initialized view controller. - */ -- (instancetype)initWithDartBundle:(NSBundle*)dartBundle; - -/** - * Initialze the view controller using the specified framework bundle - * containing the precompiled dart code. - * - * @param dartBundleOrNil the framework bundle containing the precompiled Dart - * code. - * @param nibNameOrNil the nib name. - * @param nibBundleOrNil the bundle containing the nib. - * - * @return the initialized view controller. - * - * @discussion this is the designated initializer for this class. Subclasses - * must call this method during initialzation. - */ -- (instancetype)initWithDartBundle:(NSBundle*)dartBundleOrNil - nibName:(NSString*)nibNameOrNil - bundle:(NSBundle*)nibBundleOrNil +- (instancetype)initWithProject:(FlutterDartProject*)project + nibName:(NSString*)nibNameOrNil + bundle:(NSBundle*)nibBundleOrNil NS_DESIGNATED_INITIALIZER; @end