This is the 15th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Embedding a Flutter is generally not recommended in native projects, but it can be supported in this way. Let’s look at the implementation below.
Engineering configuration of native embedded Flutter
As shown in the figure, if we want to embed the native Flutter, when creating a project with Android Studio, we will select Module to create the Flutter and develop it as a Module.
Open our newly created Flutter_module project directory and you can see that compared with the Flutter App that we created, there are Android and iOS project files in the files. However, these files are only for debugging and are hidden files. However, native code is not recommended for Android and iOS projects, and even if it is added, it will not be included in the package. Flutter_module is a pure Flutter project.
Podfile
Configuration file
flutter_application_path = '.. /flutter_module' load File.join(flutter_application_path,'.iOS','Flutter','podhelper.rb') platform :ios, '9.0' target 'NativeDemo' do install_all_FLutter_PODS (Flutter_application_path) USe_frameworks! # Pods for NativeDemo endCopy the code
We use Xcode to create a native project, NativeDemo, use terminal, CD to NativeDemo directory, pod init, then configure Podfile, and then execute Pod Install.
After pod install is complete, open the native project and reference the header file #import
. Embed the Flutter into the primary project.
Native project brings up the Flutter page
- Native code section
#import "ViewController.h" #import <Flutter/Flutter.h> @interface ViewController () @property(nonatomic, strong) FlutterEngine* flutterEngine; @property(nonatomic, strong) FlutterViewController* flutterVc; @property(nonatomic, strong) FlutterBasicMessageChannel * msgChannel; @end @implementation ViewController -(FlutterEngine *)flutterEngine { if (! _flutterEngine) { FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hank"]; if (engine.run) { _flutterEngine = engine; } } return _flutterEngine; } - (IBAction)pushFlutter:(id)sender { self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen; FlutterMethodChannel = [FlutterMethodChannel methodChannelWithName:@"one_page" binaryMessenger:self.flutterVc.binaryMessenger]; // Tells the corresponding page of Flutter [methodChannel invokeMethod:@"one" arguments:nil]; / / the pop-up page [self presentViewController: self flutterVc animated: YES completion: nil]; [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {// If it is exit I will exit the page! if ([call.method isEqualToString:@"exit"]) { [self.flutterVc dismissViewControllerAnimated:YES completion:nil]; } }]; } - (IBAction)pushFlutterTwo:(id)sender { self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen; FlutterMethodChannel = [FlutterMethodChannel methodChannelWithName:@"two_page" binaryMessenger:self.flutterVc.binaryMessenger]; // Tells the corresponding page of Flutter [methodChannel invokeMethod:@"two" arguments:nil]; / / the pop-up page [self presentViewController: self flutterVc animated: YES completion: nil]; [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {// If it is exit I will exit the page! if ([call.method isEqualToString:@"exit"]) { [self.flutterVc dismissViewControllerAnimated:YES completion:nil]; } }]; } - (void)viewDidLoad { [super viewDidLoad]; self.flutterVc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil]; self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVc.binaryMessenger]; [self.msgChannel setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) {NSLog(@" received a Flutter message: %@",message); }]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { static int a = 0; [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]]; }Copy the code
In the native code part, we defined three attributes: flutterEngine represents the engine object, flutterVc is the controller object of the type FlutterViewController, msgChannel is a channel in the communication mode, For FlutterBasicMessageChannel type, the following will be introduced.
Here we implement pushFlutter and pushFlutterTwo methods, which call up two different Flutter pages. In these two methods, we first create methodChannel object, and introduced into one with two logo, two strings and binaryMessenger preach and incoming self. FlutterVc. BinaryMessenger. Call the invokeMethod method in both methods, send a message to the Flutter page, then pop up the page, and implement the setMethodCallHandler method. Call. Method isEqualToString:@”exit” in the closure to exit the page.
- Flutter code part
// ignore_for_file: avoid_print import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final MethodChannel _oneChannel = const MethodChannel('one_page'); final MethodChannel _twoChannel = const MethodChannel('two_page'); final BasicMessageChannel _messageChannel = const BasicMessageChannel('messageChannel', StandardMessageCodec()); String pageIndex = 'one'; @override void initState() { super.initState(); _messageChannel. SetMessageHandler ((the message) {print (' received $message from iOS); return Future(() {}); }); _oneChannel.setMethodCallHandler((call) { pageIndex = call.method; print(call.method); setState(() {}); return Future(() {}); }); _twoChannel.setMethodCallHandler((call) { pageIndex = call.method; print(call.method); setState(() {}); return Future(() {}); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: _rootPage(pageIndex), ); } // return to page according to pageIndex! Widget _rootPage(String pageIndex) { switch (pageIndex) { case 'one': return Scaffold( appBar: AppBar( title: Text(pageIndex), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { _oneChannel.invokeMapMethod('exit'); }, child: Text(pageIndex), ), TextField( onChanged: (String str) { _messageChannel.send(str); }, ) ], ), ); case 'two': return Scaffold( appBar: AppBar( title: Text(pageIndex), ), body: Center( child: ElevatedButton( onPressed: () { _twoChannel.invokeMapMethod('exit'); }, child: Text(pageIndex), ), ), ); default: return Scaffold( appBar: AppBar( title: Text(pageIndex), ), body: Center( child: ElevatedButton( onPressed: () { const MethodChannel('default_page').invokeMapMethod('exit'); }, child: Text(pageIndex), ), ), ); }}}Copy the code
The variables _oneChannel and _twoChannel are defined in the Flutter code to receive and send messages to the native page. The variable pageIndex is defined to identify which page was created.
Call the setMethodCallHandler method in the initState method, get the data from the native page and assign it to pageIndex, then call the setState method.
In the build method we call the _rootPage method to determine which page to create. And call the invokeMapMethod method in the click event of each page, representing the exit page, Native page after receive the exit data in setMethodCallHandler closure is called [self. FlutterVc dismissViewControllerAnimated: YES completion: nil]. Exit the page.
Flutter communicates with native
MethodChannel
:Flutter
与Native
End call each other, call can return the result, yesNative
End active call, also canFlutter
Active call, belongs to bidirectional communication. This method is the most commonly used method,Native
Side calls need to be made in the main thread.BasicMessageChannel
: Used to encode and decode messages using the specified codec. It is two-way communicationNative
End active call, also availableFlutter
Active invocation.EventChannel
: For data flow (event streams
),Native
The end actively sends data toFlutter
, usually used for monitoring status, such as network changes, sensor data, etc.
There are three ways of Flutter and native communication. Flutter provides us with three channels, namely MethodChannel, BasicMessageChannel and EventChannel. But the two that we use a lot are MethodChannel and BasicMessageChannel. Because we’ve already covered MethodChannel, here’s how to use BasicMessageChannel.
BasicMessageChannel usage
BasicMessageChannel is used similarly to FlutterMethodChannel, and in the code example above, First, in the Flutter code we also define a variable of type BasicMessageChannel _messageChannel Messages from the native page are received in the setMessageHandler closure of _messageChannel, and the _messageChannel send method is called to communicate with the native page. The send method is called whenever the input field text changes. In native code, the msgChannel property is defined, the block in setMessageHandler receives messages, the sendMessage sends messages, and the touchesBegan passes the sum of A to the Flutter.