The opening

Start with one chart, everything else depends on _?

At present, the Flutter framework is relatively new and Google home, so the articles on the Internet are mainly about android and Flutter hybrid development. There are no detailed practical steps for iOS and Flutter hybrid development.

The premise of this mix is that your computer must have the Flutter environment. If you can’t configure the flutter environment, please check this mix tutorial after you have configured it on Google.

To the chase

Since this article is about iOS, the normal environment should be macOS + Xcode + Flutter environment (V0.8.2 -beta). Add the Dart language editor Android Studio, IntelliJ IDEA, or Visual Studio Code (VS Code) that Flutter requires. Because Flutter is multi-platform, you also need to install an Android-related SDK.

This tutorial is based on the FLUTTER environment version V0.8.2 -beta

After the environment is configured, enter the following command line: Flutter doctor -v to ensure that flutter, Android toolchain, iOS Toolchain, Connected Devices Those who qualify can go onto those who qualify. This list is only available when you open your Xcode vm or Android VM. None of these can qualify if your environment is OK.

Xcode project configuration

Start of Xcode project

Of course, the most authoritative tutorial of Flutter is its own hybrid wiki. My English understanding of iOS is not very good, so I followed the tutorial for practical operation and summarized it with the online tutorial, all the way out.

  • Flutter hybrid development does not yet support Bit Code, so check the project on iOS and turn bit Code off

  • Create a Flutter Module (do not attach the flutter module to the Xcode project, preferably to the same directory as the project)

This should also be a very new branch because the FLUTTER environment (V0.82-beta) is used here. Read the website that the master of Flutter is the newest branch. Create a Module with beta. If the module fails, switch to the Master branch

If the creation fails, switch to the master branch and try. Execute flutter Channel master

  • Analysis of important files on flutter Module (some of them are hidden files)

  • Create the Config file in the Xcode project and direct it to the Flutter Module

    Create the Config directory, manage the configuration connection files of Xcode project, and create three configuration files including Flutter. Xcconfig, Debug.xcconfig, and release. xcconfig. Xcconfig refers to the Generated. Xcconfig file that points to the external directory of the Flutter module. The other two files represent the environment configuration files of Xcode.

  • The introduction of the three files (all referenced areAn absolute path, are ultimately guided toGenerated.xcconfig)

In Flutter.xcconfig:

#include ".. /.. /flutter_module/.ios/Flutter/Generated.xcconfig" ENABLE_BITCODE=NOCopy the code

In Debug.xcconfig:

#include "Flutter.xcconfig"
Copy the code

In Release.xcconfig:

#include "Flutter.xcconfig"
FLUTTER_BUILD_MODE=release
Copy the code

It is important to note that if you are using pod to manage your project, debug. xcconfig and release.xcconfig both need to add a pod reference line

  • Xcode Project environment configuration selection

  • Most important: introductionxcode-backend.sh

Add the “$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh” build in the iOS project. Make sure the Run Script line comes after “Target dependencies” or “Check Pods manifest.lock “.

If you click Run of Xcode, the xcode-backend.sh script is displayed. At this point, in the iOS project directory, a Flutter folder is also generated, which contains the products of the Flutter project (this is the final product of the Flutter interaction with Native).

  • Add flutter compilation products

Right-click project -add Files to ‘XXX’ and select the Flutter directory

However, flutter_assets cannot be added by Create groups, but can only be added to Xcode project by Creat Folder References, otherwise the page rendering will fail when jumping to flutter (page blank).

flutter_assets
Add Files to 'xxx'
Creat folder references

Add the Flutter folder from the iOS project directory to the project, and then make sure that the two frameworks in that folder are added to the Embeded Binaries

At this point, the mix configuration between Xcode and Flutter is complete and the two project files are associated. Dart can then be modified to run Xcode. App. framework will be automatically compiled into the latest Flutter code.

Use POD to manage situations in projects

First, the old project did not use POD management, and then tried to use POD management after mixing

  • 1, Delete the Run Script from Xcode project
  • 2,pod init
  • 3, in the generated pod file write the third party framework you want to add, such asPod 'AFNetworking'
  • 4,pod install
  • 5. (open the project using.xcworkspace) reconfigure the Run Script
  • 6, modify,Debug.xcconfigThe Release. xcconfigNeed to beincreaseA line of pod config file reference :(check your own Pods directory file path and use.relex.xcconfig for release)
#include "Flutter. Xcconfig "// Here is the line to add to pod #include "Pods/Target Support Files/Pods-iOSBridgeFlutterDemo/Pods-iOSBridgeFlutterDemo.debug.xcconfig"Copy the code
  • 7. Project recompile, Success

Second, if the old project has been managed by POD

  • If the project ignore Pods folder, follow steps 1, 4, 5, 6, and 7 in the method
  • If the project Pods folder exists, follow steps 6 and 7 in a method

PS: Every pod update or pod install will give an error because of the Run Script, so every time you add or update the pod, you have to delete the Run Script, update the pod and add back the Run Script. These tedious operations do not know whether there is a way to avoid, know friends can reply? Thank you!

Xcode interacts with Flutter

AppDelegate transformation

transformAppDelegate.hAnd make it inheritFlutterAppDelegate:

#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate, FlutterAppLifeCycleProvider>

@end
Copy the code

transformAppDelegate.m

#import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate { FlutterPluginAppLifeCycleDelegate *_lifeCycleDelegate; } - (instancetype)init { if (self = [super init]) { _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; } return self; } - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions]; } - (void)applicationDidEnterBackground:(UIApplication*)application { [_lifeCycleDelegate applicationDidEnterBackground:application]; } - (void)applicationWillEnterForeground:(UIApplication*)application { [_lifeCycleDelegate applicationWillEnterForeground:application]; } - (void)applicationWillResignActive:(UIApplication*)application { [_lifeCycleDelegate applicationWillResignActive:application]; } - (void)applicationDidBecomeActive:(UIApplication*)application { [_lifeCycleDelegate applicationDidBecomeActive:application]; } - (void)applicationWillTerminate:(UIApplication*)application { [_lifeCycleDelegate applicationWillTerminate:application]; } - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { [_lifeCycleDelegate application:application didRegisterUserNotificationSettings:notificationSettings]; } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { [_lifeCycleDelegate application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { return [_lifeCycleDelegate application:application openURL:url options:options]; } - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { return [_lifeCycleDelegate application:application handleOpenURL:url]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation { return [_lifeCycleDelegate application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; } - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { [_lifeCycleDelegate application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; } - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString*)identifier completionHandler:(nonnull void (^)(void))completionHandler { [_lifeCycleDelegate application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; } - (void)application:(UIApplication*)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler]; } - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate { [_lifeCycleDelegate addDelegate:delegate]; } #pragma mark - Flutter // 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; } return nil; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { [super touchesBegan:touches withEvent:event]; // Pass status bar taps to key window Flutter rootViewController. if (self.rootFlutterViewController ! = nil) { [self.rootFlutterViewController handleStatusBarTouches:event]; } } @endCopy the code

Flutter active, Native passive

Flutter code: the introduction of the import ‘package: Flutter/services. The dart’;

Replace the contents of the class _MyHomePageState extends State

with the following code

class _MyHomePageState extends State<MyHomePage> {

  // Create a channel for Native (iOS like notification)
  static const methodChannel = const MethodChannel('com.pages.your/native_get');

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;

      print('flutter log print: now print count=$_counter');
      // Send a parameter to the client when the number reaches 3
      if(_counter == 3) {
        _toNativeSomethingAndGetInfo();
      }

      // Send a parameter to the client when the number reaches 5
      if(_counter == 1002) {
        Map<String.String> map = { "title": "This is a parameter from Flutter." };
        methodChannel.invokeMethod('toNativePush',map);
      }

      // Send a parameter to the client when the number reaches 8
      if(_counter == 1005) {
        Map<String.dynamic> map = { "content": "FlutterPop back"."data": [1.2.3.4.5]};
        methodChannel.invokeMethod('toNativePop',map); }}); }// Send something to the client and get something back
  Future<Null> _toNativeSomethingAndGetInfo() async {
    dynamic result;
    try {
      result = await methodChannel.invokeMethod('toNativeSomething'.'You clicked under $_counter');
    } on PlatformException {
      result = 100000;
    }
    setState(() {
      // Type judgment
      if (result is int) { _counter = result; }}); }@override
  Widget build(BuildContext context) {

    return new Scaffold(
// appBar: new AppBar(
// // Here we take the value from the MyHomePage object that was created by
// // the App.build method, and use it to set our appbar title.
// title: new Text(widget.title),
/ /),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',),new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.); }}Copy the code

Native code:

- (void)pushFlutterViewController { FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; flutterViewController.navigationItem.title = @"Flutter Demo"; __weak __typeof(self) weakSelf = self; NSString *channelName = @"com.pages. Your /native_get"; FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController]; [messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {// call.method gets the method name that flutter gives back. // Result is a callback to a flutter, which can only be used once NSLog(@"flutter to me: \nmethod=%@ \narguments = %@",call.method,call.arguments);  if ([call.method isEqualToString:@"toNativeSomething"]) { UIAlertView *alertView = [[UIAlertView alloc] InitWithTitle :@"flutter callback "message:[NSString stringWithFormat:@"%@",call.arguments] delegate:self CancelButtonTitle :@" sure "otherButtonTitles:nil]; [alertView show];  } } else if ([call.method isEqualToString:@"toNativePush"]) { ThirdViewController *testVC = [[ThirdViewController alloc] init]; testVC.parames = call.arguments; [weakSelf.navigationController pushViewController:testVC animated:YES];  } else if ([call.method isEqualToString:@"toNativePop"]) { [weakSelf.navigationController popViewControllerAnimated:YES]; } }]; [self.navigationController pushViewController:flutterViewController animated:YES]; }Copy the code

Native active, Flutter passive (EventChannel)

Generally used for flutter initialization, some parameters are obtained from the client as rendering conditions. Similar to the KVO on iOS, it listens to see if the flutter is already listening and calls back to the agent. IOS Native processes the agent and calls back the parameters required by the flutter

Flutter code (in class) :

  // Register a notification
  static const EventChannel eventChannel = const EventChannel('com.pages.your/native_post');

  // Before rendering, similar to viewDidLoad
  @override
  void initState() {
    super.initState();
    
    // Listen for the event and send the argument 12345
    eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
  }

  String naviTitle = 'Hello, Big man.' ;
  // Callback event
  void _onEvent(Object event) {
    setState(() {
      naviTitle =  event.toString();
    });
  }
  // Error return
  void _onError(Object error) {

  }
Copy the code

Native code:

- (void)pushFlutterViewController_EventChannel { FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; flutterViewController.navigationItem.title = @"EventChannel Demo"; NSString *channelName = @"com.pages. Your /native_post"; FlutterEventChannel *evenChannal = [FlutterEventChannel eventChannelWithName:channelName binaryMessenger:flutterViewController]; // proxy [evenChannal setStreamHandler:self]; [self.navigationController pushViewController:flutterViewController animated:YES]; } #pragma mark - <FlutterStreamHandler> // // - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)events { // If (events) {events(@" I am the title "); if (events) {events(@" I am the title "); } return nil; } // flutter no longer receives - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {// arguments The argument that flutter gives to native return nil; }Copy the code

Interactive summary

The two methods are similar, one using block, the other using delegate; The final callback to flutter is through the block.

MethodChannel uses blocks, which makes the context more explicit; The same channel name can be used only once, depending on the call.method and call.arguments returned by flutter. The flutter block is no longer received after the callback.

EventChannel uses a delegate to create a more distinct code hierarchy; The same channel name can only handle the callback handle using the arguments argument. The callback can be used multiple times (create an instance pointing to a block that can send multiple times to a flutter).

BasicMessageChannel please learn by yourself.

doubt

Memory created using FlutterViewController Xcode keeps increasing to a level where class overwrites – (void)dealloc does not come in, probably a Memory leak. So I went to check the official Issues, and there are indeed several links:

  • Flutter SSL Memory Leaks #20409
  • Unable to release FlutterViewController even when there is nothing referencing it. #21347

Native simple push FlutterViewController, pop back, memory reaches a stage does not fall, FlutterViewController will not execute dealloc method. Who knows if there is a solution? Thank you!

learning

  • Flutter was developed and scripted with existing iOS projects

  • Gain insight into the Flutter Platform Channel

  • Because Chinese website

  • Flutter from environment building to advanced series of tutorials

  • In this paper, the Demo