Flutter plugin development

Preface

I have been doing iOS development for many years. Although I have encountered many problems and solved many problems, they have not been recorded, because I am lazy, and sometimes I forget because of my busy work, and I have not written or maintained iOS related issues. I am deeply ashamed of this technical article without thinking about it.It has been a long time since Flutter was released from 1.0. There are also many companies that have begun to try to be crabby people. It is as rich as native iOS or native Android. Sometimes PM needs are so difficult to deal with. It is inevitable that you need to develop plug-ins by yourself. Then I will share my little experience in developing plug-ins, hoping to help some friends.

A preparation

I won't go into details about building the Flutter environment here. To create a new component template, there are two ways to create one, one is through the command line, the other is through Android Studio

Create via command line:

flutter create -t plugin flutter_plugin
复制代码

Android Studio creation

Select the second item Start a new Flutter project

 

Choose Flutter Plugin

Android second step.jpg

 

Fill in the plugin name

3.jpg

 

The place where the red circle rises is selected according to your own language. The default Android project is java. The iOS project is Object-c. If you need Kotlin and Swift, you must tick it. I have always used Object-c.

4.jpg

 

This is the newly created plugin template directory

5.jpg

 

If you write native code, find the corresponding iOS or Android directory in the example directory. Take iOS as an example. The newly created iOS code does not contain the pod folder, so it can’t run. You need to run pod install in the iOS folder in the example first. To install the required dependencies

 

 

6.jpg

 

 

Then you can run the sample project

7.jpg

 

 

At this point, the preparation for Flutter plug-in development is over, let's enter the real plug-in development.

Grind the fist Huo Huo Xiang Zhu Yang-plug-in development

Let's start with the Dart code first

This is the dart code just created by the template

8.jpg

 

 

Let's simplify the code first, delete the useless code, and then comment out the related error code

///先定义一个controller创建成功回调 后面会用到
typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {
  MethodChannel _channel;
  TestFlutterPluginDemo.init(int id){
    _channel = new MethodChannel('test_flutter_plugin_demo');
  }
}
复制代码

TestFlutterPluginDemo This class I understand is mainly responsible for interacting with native, calling native methods and monitoring native methods, and then let's create a new view class mainly to load native views

///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
  ///根据自己的需求创建初始化参数
  final TestViewCreatedCallback onCreated; ///是上面创建的回调
  final String titleStr;

  TestView({
    Key key,
   this.onCreated,
   this.titleStr,
});

  @override
  _TestViewState createState() => _TestViewState();
}

class _TestViewState extends State<TestView> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _loadNativeView(),
    );
  }
///加载原生视图
  Widget _loadNativeView(){
    ///根据不同的平台显示相应的视图
    if(Platform.isAndroid){ ///加载安卓原生视图
      return AndroidView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else if(Platform.isIOS){///加载iOS原生视图
      return UiKitView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else{
      return Text('这个平台老子不支持');
    }
  }
  ///我也是刚学Flutter 所以我理解的:这个基本上是固定写法   哈哈
  Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
    }
    widget.onCreated(new TestFlutterPluginDemo.init(id));
  }
}
复制代码

Up to now, the main code of Dart has been written. The comments in the code are very detailed. Friends can follow the code step by step to enhance memory and code familiarity. The rest is to load in main.dart View,

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

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
 
  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo;

  @override
  void initState() {
    super.initState();

  }
  
  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );
    
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          children: <Widget>[
            Container(
              height: 200,
              width: 400,
              child: testView,///使用原生视图
            )
          ],
        )
      ),
    );
  }

  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }
}
复制代码

After adding the code to load the view in main.dart, the plug-in development Dart related code is finished, let's start writing the native code, and emphasize that the iOS code is an example of the OC language

Start native code

Let us first look at the code file directory of the test project

9.jpg

 

 

It was found that there are two files GeneratedPluginRegistrant.h and GeneratedPluginRegistrant.m, but these two files are not the pages where we need to type code. The pages where we type code are hidden deeper in the following directory

10.jpg

 

Classes must find the file in the pod / Development Pods folder inside the development of these two documents, but also create a new class in this directory, made components used to develop iOS students must be very familiar with, the principle is similar yo good start native code development Let's first create a new Factory to connect to Flutter's views. The new factory should inherit from NSObject, and then when creating a new view class, it should also inherit from NSObject to write the code for the two classes of native page layout as follows

 

factory.h

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface TestFlutterPluginViewFactory : NSObject<FlutterPlatformViewFactory>

/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;

@end
复制代码

factory.m

#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()

@property(nonatomic)NSObject<FlutterBinaryMessenger>* messenger;

@end

@implementation TestFlutterPluginViewFactory

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
    self = [super init];
    if (self) {
        self.messenger = messenger;
    }
    return self;
}

#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}

/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
    
    TestFlutterPluginView *testFlutterPluginView = [[TestFlutterPluginView alloc] initWithFrame:frame viewId:viewId args:args messager:self.messenger];
    return testFlutterPluginView;
    
}

@end
复制代码

testView.h

#import <Foundation/Foundation.h>
#include <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface TestFlutterPluginView : NSObject<FlutterPlatformView>
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
复制代码

testView.m

#import "TestFlutterPluginView.h"

@interface TestFlutterPluginView ()
/** channel*/
@property (nonatomic, strong)  FlutterMethodChannel  *channel;
@end

@implementation TestFlutterPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
   
}

- (id)initWithFrame:(CGRect)frame
  viewId:(int64_t)viewId
    args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"test_flutter_plugin_demo" binaryMessenger:messenger];
        
        __weak __typeof__(self) weakSelf = self;

        [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            [weakSelf onMethodCall:call result:result];
        }];
       
    }
    return self;
}

- (UIView *)view{
    
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
    nativeView.backgroundColor = [UIColor redColor];
    
    return nativeView;
     
}

#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    
}
复制代码

Factory class and view classes are created, it is now possible to create a template in the beginning of this class do TestFlutterPluginDemoPlugin the association is / Development Pods find these two files inside the Classes folder in the deleted useless code that comes with The example code of the connection is as follows: TestFlutterPluginDemoPlugin.h

#import "TestFlutterPluginDemoPlugin.h"
#import "TestFlutterPluginViewFactory.h"

@implementation TestFlutterPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  
    TestFlutterPluginViewFactory *testViewFactory = [[TestFlutterPluginViewFactory alloc] initWithMessenger:registrar.messenger];
//这里填写的id 一定要和dart里面的viewType 这个参数传的id一致
    [registrar registerViewFactory:testViewFactory withId:@"testView"];
    
}

@end
复制代码

Now the code to display the view is finished. If you want to load successfully, add it to info.plist

	<true/>
复制代码

Now let's run it to see the effect

11.jpg

 

 

Loading the native view is now complete. Let's start to learn how to call and pass values ​​between Flutter methods.

Flutter and native interaction

Let's write the Dart code first and add the native interaction code to the TestFlutterPluginDemo class we created above

typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {

  MethodChannel _channel;
  TestFlutterPluginDemo.init(int id){
    _channel = new MethodChannel('test_flutter_plugin_demo');
    _channel.setMethodCallHandler(platformCallHandler);///设置原生参数监听
  }

  ///Flutter 调用原生
  ///这里我传了一个 字符串 当然也可以传Map
  Future<void> changeNativeTitle(String str) async{
    return _channel.invokeListMethod('changeNativeTitle',str);
  }

  ///实现监听原生方法回调
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "clickAciton":
        print('收到原生回调 ---- $call.arguments');
        return ;
        break;
    }
  }
}
复制代码

Then let's add a button in main.dart to trigger the call to native

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo;

  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          children: <Widget>[
            Container(
              height: 200,
              width: 400,
              child: testView,///使用原生视图
            ),
            FloatingActionButton( ///添加一个按钮 用来触发原生调用
              onPressed: onNativeMethon, ///点击方法里面调用原生
            )
          ],
        )
      ),
    );
  }

  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }

  void onNativeMethon(){

    this.testFlutterPluginDemo.changeNativeTitle('Flutter 调用原生成功了');
  }

}
复制代码

The Dart code is finished, let's write the native code. Add a button to the testView we just created to display the call of Flutter and call Flutter, and then listen to the Flutter call in the onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result method.

- (UIView *)view{
    
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
    nativeView.backgroundColor = [UIColor redColor];
    
    _button = [UIButton buttonWithType:UIButtonTypeSystem];
    [_button setTitle:@"我是按钮" forState:UIControlStateNormal];
    [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_button setBackgroundColor:[UIColor lightGrayColor]];
    _button.frame = CGRectMake(100, 100, 100, 100);
    [nativeView addSubview:_button];
    [_button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];
    return nativeView;
     
}

#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    //监听Fluter
    if ([[call method] isEqualToString:@"changeNativeTitle"]) {
        [_button setTitle:call.arguments forState:UIControlStateNormal];
    }
    
}
//调用Flutter 
- (void)flutterMethod{
    [self.channel invokeMethod:@"clickAciton" arguments:@"我是参数"];
}
复制代码

Now let's run and see the effect

246050b9-fc17-48c4-b482-35a07d869e8c.gif

 

12.jpg

 

At this point, the plug-in development is complete, just add the code according to your needs.
 

Guess you like

Origin blog.csdn.net/u013491829/article/details/109351940