The Release Preview 1 version of Flutter has attracted a lot of attention. It’s easy to write an App from scratch with Flutter, but more and more people are finding that Flutter doesn’t seem to be friendly enough to support existing apps. So this article takes you through how to get existing apps to support Flutter.

The environment

Build a Flutter: 0.5.1 Xcode 9.4.1 Flutter project: Flutter /examples/hello_world

Debug: Flutter Hot Restart

We all know that Hot Restart for Flutter is very helpful for RAPID prototyping of UI in a development environment. It is not difficult to get existing apps to support Flutter and enable Hot Restart.


Flutter based library (engine)

First, drag the Flutter. Framework into your project. This library is the Flutter Engine, which hosts the Dart runtime and drawing Engine. There is a one-to-one correspondence between the Flutter. Framework and the command line tool versions. If you do not know where to find the file, you can directly perform a Flutter run in the Flutter source project. You can then find Flutter under / /ios/Flutter/. Just drag the project into Flutter.


Flutter ViewController

Then we need to introduce the underlying code of the Flutter into the existing project. Only with the underlying Flutter ViewController can we display the Flutter view. This step is as simple as pushing in your existing ViewController:

- (void)jumpToFlutter {
    FlutterViewController *viewController = [FlutterViewController new];
    [self.navigationController pushViewController:viewController animated:YES];
}
Copy the code

Note, however, that you need to pass life cycle events from the AppDelegate to Flutter


After the Release Preview 1 document have FlutterAppLifeCycleProvider mentioned in this agreement, but 0.5.1: not yet, so this approach first

  1. The existing AppDelegate can inherit from the FlutterAppDelegate directly, but this has the negative effect that the root ViewController is set to the Flutter ViewController

  2. Modify the AppDelegate yourself. Fortunately, Flutter APPDelegate is not too difficult to adapt and can also support module Delegate. Start by having your Delegate follow the FlutterPluginRegistry protocol

/ / AppDelegate or Delegate @ interface module DemoFlutterBaseAppDelegate: NSObject <ModularApplicationDelegate, FlutterPluginRegistry> /** FlutterBinaryMessenger this determines which view controller is the flutter view controller nomally, flutter view controller provides the binary messages @return root Flutter ViewController */ - (NSObject<FlutterBinaryMessenger> *)binaryMessenger; /** FlutterTextureRegistry this determines which view controller is the flutter view controller nomally, flutter view controller provides the custom textures @return root Flutter ViewController */ - (NSObject<FlutterTextureRegistry> *)textures; @endCopy the code
 





/ / the Registrar declaration and implementation @ interface DemoFlutterAppDelegateRegistrar: NSObject<FlutterPluginRegistrar> @property(nonatomic, copy) NSString *pluginKey; @property(nonatomic, strong) DemoFlutterBaseAppDelegate *appDelegate; - (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)delegate; @end @implementation DemoFlutterAppDelegateRegistrar - (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)appDelegate { self = [super init]; NSAssert(self, @"Super init cannot be nil "); _pluginKey = [pluginKey copy]; _appDelegate = appDelegate; return self; } - (NSObject<FlutterBinaryMessenger>*)messenger { return [_appDelegate binaryMessenger]; } - (NSObject<FlutterTextureRegistry>*)textures { return [_appDelegate textures]; } - (void)publish:(NSObject*)value { _appDelegate.pluginPublications[_pluginKey] = value; } - (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate channel:(FlutterMethodChannel*)channel { [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [delegate handleMethodCall:call result:result]; }]; } - (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate { [_appDelegate.pluginDelegates addObject:delegate];  } - (NSString*)lookupKeyForAsset:(NSString*)asset { return [FlutterDartProject lookupKeyForAsset:asset]; } - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package]; } @ the end / / AppDelegate @ interface DemoFlutterBaseAppDelegate () @ property (nonatomic, strong) DemoFlutterBaseViewController *rootController; @property(readonly, nonatomic) NSMutableArray* pluginDelegates; @property(readonly, nonatomic) NSMutableDictionary* pluginPublications; @end @implementation DemoFlutterBaseAppDelegate /* * ... Here write forward various statement cycle events to the plugin * / - (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { for (id<FlutterPlugin> plugin in _pluginDelegates) { if ([plugin respondsToSelector:_cmd]) { [plugin application:application didFinishLaunchingWithOptions:launchOptions]; } } return YES; } #pragma mark-getters for flutter - (FlutterViewController *)rootController {//... } - (NSObject<FlutterBinaryMessenger> *)binaryMessenger { if ([self.rootController conformsToProtocol:@protocol(FlutterBinaryMessenger)]) { return (NSObject<FlutterBinaryMessenger> *)self.rootController;  } return nil; } - (NSObject<FlutterTextureRegistry> *)textures { if ([self.rootController conformsToProtocol:@protocol(FlutterTextureRegistry)]) { return (NSObject<FlutterTextureRegistry> *)self.rootController;  } return nil; } - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey { NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@ ", pluginKey); self.pluginPublications[pluginKey] = [NSNull null]; return [[DemoFlutterAppDelegateRegistrar alloc] initWithPlugin:pluginKey appDelegate:self]; } - (BOOL)hasPlugin:(NSString*)pluginKey { return _pluginPublications[pluginKey] ! = nil; } - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { return _pluginPublications[pluginKey]; } @endCopy the code


This way the Flutter environment is ready to run, both Hot Restart and AOT can support it. The Debug Hot Restart will first execute the build bundle of a Flutter Asset in your Flutter code directory. This will help us package a Flutter Asset. Then drag the flutter_assets directory into the project.

Build your project and make sure you get an.app file. Create a new folder called Payload, place the.app file in the Payload folder, and compress it into a ZIP file. This file can then be used by the Flutter command line tool.

flutter run --use-application-binary /path/to/Payload.zip
Copy the code

Then the effect is shown as follows:





There is no Hot Reload implementation. The current support for the Flutter tool chain is not good, but the Hot Restart can be used.

Release: Flutter AOT

Release mode is different from Debug, we need to do a few things:

  1. theFlutter.frameworkreplaceflutter/bin/cache/artifacts/engine/ios-release/Flutter.frameworkBecause the library we used in the previous step was actually the JIT Runtime
  2. Execute under the Flutter code projectflutter build aot --release --target-platform ios --ios-arch armv7,arm64Then we can get a packaged one in the build directoryApp.frameworkBut don’t forget to put an info.plist in it. And drag the library into the project
  3. Delete from projectflutter_assetsUnder folderisolate_snapshot_data,kernel_blob.bin,platform.dill,vm_snapshot_dataThese files
  4. Compile and package to the real machine to run, the effect is as follows:




The End

In fact, Flutter has an official plan to support the integration of existing apps, and part of the documentation is also introduced. However, the overall tool chain is not supported yet, and the degree of support is similar to the above method. If necessary, existing projects can use the above method of integration, in order to reduce workflow, some work needs to be done, such as:

  1. The project provides empty shells of App. Framework and Flutter. Framework to be easily replaced with scripts at any time under debug and release
  2. You can also add a build Post action that calls the Flutter command line directly to integrate Xcode with the Flutter. Do not manually Debug all the Flutter processes as described above
  3. Automation of Release AOT will definitely need to be done and integrated with existing CI’s