Flutter and iOS hybrid development practice

  • First, create the Flutter module;
  • Add Flutter module dependencies to existing iOS applications;
  • Call Flutter module in Object-c;
  • Write Dart code;
  • Run the project;
  • Hot restart/reload;
  • Debugging Dart code;
  • publish applications;
- flutter_hybrid
- flutter_module
- FlutterHybridAndroid
- FlutterHybridiOS

Below flutter_hybrid are the flutter module, the native Android module, and the native iOS module, and these three modules are in parallel structure.

1. Create Flutter module

Before doing hybrid development, we first need to create a Flutter module.

If your Native project looks like this: xxx/flutter_hybrid/FlutterHybridiOS:

$ cd xxx/flutter_hybrid/
$ flutter create -t module flutter_module

The above code will switch to the upper-level directory of your iOS project and create a flutter module:

//flutter_module/
.android
.gitignore
.idea
.ios
.metadata
.packages
build
flutter_module_android.iml
flutter_module.iml
lib
pubspec.lock
pubspec.yaml
README.md
test

The above is the file structure in flutter_module. You will find that it contains .android and .ios. These two folders are hidden files. This is also the flutter_module host project:

  • .android – Android host project of flutter_module;
  • .ios – iOS host project of flutter_module;
  • lib – The code for the Dart part of flutter_module;
  • pubspec.yamlflutter_module‘s project dependency configuration file;

Because of the existence of the host project, our flutter_module can run independently without additional configuration. Open this flutter_module through AndroidStudio with Flutter and Dart plug-ins installed. Project, you can run it directly through the run button.

2. Add Flutter module dependencies to existing iOS applications

Next we need to configure the Flutter module dependencies of our iOS project. The next configuration requires CocoaPods. If you have not used CocoaPods yet, you can refer to the instructions above on CocoaPods.org to install CocoaPods.

2.1 Add flutter dependencies in the Podfile file

If there is no Podfile file in your iOS project, you can pass:

pod init

Initialize one.

Next add the script:

Check your Flutter version through the flutter doctor command. Different Flutter versions require different configurations:

The following is the configuration added by Flutter >= v1.10.14 version:

-Flutter >= v1.10.14 version configuration START-

# Step1
flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
#Step2
install_all_flutter_pods(flutter_application_path)

For example:

Below is the reference configuration from the Podfile:

# Step1: Add
flutter_application_path = '../flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'FlutterHybridiOS' do
  # Step2 Add
  install_all_flutter_pods(flutter_application_path)

  target 'FlutterHybridiOSTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'FlutterHybridiOSUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

-Flutter >= v1.10.14 version configurationEND-

Configurations that need to be added in the old version of Flutter:

-Old version of Flutter configuration START-

 flutter_application_path = 'path/to/my_flutter/'
  eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

For example:

target 'FlutterHybridiOS' do
  flutter_application_path = '../flutter_module/'
  eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

  target 'FlutterHybridiOSTests' do
    inherit! :search_paths
  end

  target 'FlutterHybridiOSUITests' do
    inherit! :search_paths
  end

end

In addition, the old version of flutter also needs to add a build phase to build Dart code

Create a build phase according to the prompts in the picture above, then expand Run Script and add the following configuration:

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

Finally, remember to follow the prompts in the picture above and place Run Script next toTarget Dependencies phase, and then you can pass ?BBuild your project.

-Old version Flutter configurationEND-

2.2 Installation dependencies

Run in the root directory of your iOS project:

pod install

you will see:

pod install
Analyzing dependencies
Fetching podspec for `Flutter` from `../flutter_module/.ios/Flutter/engine`
Fetching podspec for `FlutterPluginRegistrant` from `../flutter_module/.ios/Flutter/FlutterPluginRegistrant`
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `FlutterHybridiOS.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed.

[!] Automatically assigning platform `ios` with version `12.1` on target `FlutterHybridiOS` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/ podfile.html#platform`.

When you add a Flutter plug-in in flutter_module/pubspec.yaml, you need to run it in the flutter_module directory:

flutter packages get

To refresh the plugin list in the podhelper.rb script, then run it in the iOS directory:

pod install

In this way, the podhelper.rb script can ensure that your plug-in and Flutter.framework can be added to your iOS project.

2.3 Disable Bitcode

Currently, Flutter does not support Bitcode, so iOS projects that integrate Flutter need to disable Bitcode:

Open your project with XCode such as: xxx.xcworkspace:

Then in:

Build Settings->Build Options->Enable Bitcode

Disable Bitcode in:

3. Call Flutter module in Object-c

At this point, we have added the necessary dependencies for Flutter to our iOS project. Next, let’s look at how to call the Flutter module in Object-c:

There are two ways to call Flutter module in Object-c:

  • Directly use FlutterViewController;
  • Using FlutterEngine;

3.1 Directly using FlutterViewController

// ?flutter_hybrid? ?FlutterHybridiOS? ?FlutterHybridiOS? ?ViewController.m

#import

#import “AppDelegate.h”

#import “ViewController.h”

#import // If you need to use the Flutter plug-in

FlutterViewController *flutterViewController = [FlutterViewController new]; GeneratedPluginRegistrant.register(with: flutterViewController);//If you need to use the Flutter plug-in [flutterViewController setInitialRoute:@”route1″];

[self presentViewController:flutterViewController animated:true completion:nil];

In this way we can use the flutterViewController setInitialRoute method to pass the string “route1” to tell the Dart code which widget to display in the Flutter view. The lib/main.dart file of the Flutter module project needs to obtain the route name specified by Native to be displayed through window.defaultRouteName to determine which widget to create and pass it to runApp:

import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

3.2 How to use FlutterEngine

AppDelegate.h

#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end

AppDelegate.m

#import // If you need to use the Flutter plug-in #include “AppDelegate.h”

@implementation AppDelegate

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

self.flutterEngine = [[FlutterEngine alloc] initWithName:@”io.flutter” project:nil];

[self.flutterEngine runWithEntrypoint:nil];

[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; //If you need to use the Flutter plug-in

return [super application:application didFinishLaunchingWithOptions:launchOptions]; }

@end

If your project’s AppDelegate.h already has other integrations, you can refer to the method of implementing FlutterAppLifeCycleProvider for configuration.

ViewController.m

// ?flutter_hybrid? ?FlutterHybridiOS? ?FlutterHybridiOS? ?ViewController.m

 FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:false completion:nil];

Because we initialized FlutterEngine in AppDelegate.m in advance, opening a Flutter module in this way is faster than the first way.

3.3 Pass data when calling Flutter module

In the above, whether we use FlutterViewController directly or FlutterEngine, we are allowed to pass a String type initialRoute when loading the Flutter module. Parameter, from the parameter name, it is used as the route name, but since Flutter has opened this hole for us, can we do something and pass other parameters we want to pass, such as:

 [flutterViewController setInitialRoute:@"{name:'devio',dataList:['aa','bb',''cc]}"];

Then get it in the Flutter module as follows:

import 'dart:ui';//To use the window object, it must be imported

String initParams=window.defaultRouteName;
//Serialize into Dart obj, you can do whatever you want
...

Through the explanation of the above scheme, have you shared a new idea with everyone?

Note that calling setInitialRoute when using FlutterEngine will be invalid. You will find that Dart always gets “/”. This is a bug of Flutter SDK, so if you must rely on setInitialRoute Then please use method 1;

4. Write Dart code

The next step is to write Dart under the lib in the Flutter module. Go to Enjoy Coding! ! !

5. Run the project

Next, we can run it. After the above steps, we can run an iOS project integrated with Flutter through XCode in the same way as a normal iOS project.

6. Hot restart/reload

Everyone knows that when we are doing Flutter development, it has a hot restart/reload function, but you may find that when the Flutter project is integrated into the iOS project during hybrid development, Flutter’s hot restart/reload function seems to be invalid. , then how to enable hot restart/reloading of Flutter for hybrid development:

  • Open an emulator or connect a device to your computer;
  • Close our APP, and then run flutter attach;
$ cd flutter_hybrid/flutter_module
$ flutter attach
Checking for advertised Dart observatories...
Waiting for a connection from Flutter on iPhone X...

Note that if you have multiple emulators or connected devices at the same time, running flutter attach will prompt you to select a device:

Android SDK built for x86 ? emulator-5554 ? android-x86 ? Android 8.1.0 (API 27) (emulator)
iPhone X ? 3E3FA943-715F-482F-B003-D46F5902C56C ? ios ? iOS 12.1 (simulator)

Next we need flutter attach -d to specify a device:

 flutter attach -d 'emulator-5554'

Note the device ID following -d.

Run the APP and you will see:

$ flutter attach More than one device connected; please specify a device with the ‘-d ‘ flag, or use ‘-d all’ to act on all devices. Android SDK built for x86 ? emulator-5554 ? android-x86 ? Android 8.1.0 (API 27) (emulator) iPhone -715F-482F-B003-D46F5902C56C’ Checking for advertised Dart observatories… Waiting for a connection from Flutter on iPhone X… Done. Syncing files to device iPhone X… 1,613ms ? To hot reload changes while running , press “r”. To hot restart (and rebuild state), press “R”. An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:64108/ For a more detailed help message, press “h”. To detach, press “d”; to quit, press “q”.

It means that the connection is successful. You can then use the above prompts to perform hot reloading/hot restarting. Enter in the terminal:

  • r: hot loading;
  • R: hot restart;
  • h: Get help;
  • d: disconnect;

7. Debugging Dart code

How can we debug our code better and more efficiently under the hybrid development mode? Next, I will share with you a way to debug the code efficiently under the hybrid development mode:

  • Close APP (This step is very important)
  • Click the Flutter Attach button of AndroidStudio (you need to install the Flutter and Dart plug-ins first)
  • Start APP

Next, you can debug the Dart code in the hybrid development mode just like debugging a normal Flutter project.

Except for the differences in the above steps, the following debugging is common to the Flutter debugging skills in our previous courses. Students who need it can learn from our previous courses;

One more thing to note:

Everyone must use XCode when running iOS projects, because AndroidStudio in Flutter mode runs the iOS project in .ios under the Flutter module.

8. Publish application

To publish an iOS app, we need a $99 account to upload the app to the AppStore, or a $299 enterprise-level account to publish the app to our own company’s server or a third-party company’s server.

Next, we need to apply for an APPID, create an application in Tunes Connect, package the program, and submit the application to the app store.