Skip to content

zero-li/flutter_pigeon_plugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flutter_pigeon_plugin

A new Flutter plugin use pigeon, 实现 flutter 和 native 互调( Android, iOS).

Pigeon的作用

Flutter官方提供的Pigeon插件,通过dart入口,生成双端通用的模板代码,Native部分只需通过重写模板内的接口,无需关心methodChannel部分的具体实现,入参,出参也均通过生成的模板代码进行约束。假设接口新增,或者参数修改,只需要在dart侧更新协议文件,生成双端模板,即可达到同步更新。

创建 Plugin project

创建方式:

  1. Android Studio中,创建 flutter plugin project
  2. 控制台,命令行
// The plugin project was generated without specifying the `--platforms` flag, no new platforms are added.
// To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
// directory. You can also find detailed instructions on how to add platforms in the `pubspec.yaml`
flutter create --org com.zero --template plugin flutterPigeon

// 正确
flutter create --org com.zero --template plugin  --platforms android,ios flutterPigeon

注意 创建命令需要带 --platforms android,ios,才会创建 Android iOS目录

修改 Android 配置

Android Studio 中运行,gradle 版本要求为 7.0.2,需要修改各个目录下的gradle和build配置

修改内容如下:

// android/gradle/wrapper/gradle-wrapper.properties
// example/android/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip

// android/build.gradle
// example/android/app/build.gradle
    ext.kotlin_version = '1.5.20'
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.4'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

至此,project 构建完成,可运行 flutterPlugin/example

接入Pigeon

添加依赖

首先在pubspec.yaml中添加依赖

// flutterPigeon\pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  pigeon: 9.0.4

然后按照官方的要求添加一个pigeons目录,这里我们放dart侧的入口文件,内容为接口、参数、返回值的定义,后面通过pigeon的命令,生产native端代码。

pigeons/message.dart

import 'package:pigeon/pigeon.dart';

// 输出配置
// 控制台执行:flutter pub run pigeon --input pigeons/message.dart
@ConfigurePigeon(PigeonOptions(
  dartOut: './lib/message.dart',
  kotlinOut: 'android/src/main/kotlin/com/zero/flutter_pigeon_plugin/Pigeon.kt',
  kotlinOptions: KotlinOptions(
   // copyrightHeader: ['zero'],
    package: 'com.zero.flutter_pigeon_plugin',
  ),
  //see ios/flutter_pigeon_plugin.podspec -> s.source_files = 'Classes/**/*'
  objcHeaderOut: 'ios/Classes/Pigeon.h',
  objcSourceOut: 'ios/Classes/Pigeon.m',
  objcOptions: ObjcOptions(
    prefix: 'FLT',
  ),
))

class SearchRequest {
  String query;
}

class SearchReply {
  String result;
}

/// flutter call native
@HostApi()
abstract class FlutterCallNativeApi {
  SearchReply search(SearchRequest request);
}

/// native call flutter
@FlutterApi()
abstract class NativeCallFlutterApi{
  SearchReply query(SearchRequest request);
}


message.dart文件中定义了请求参数类型、返回值类型、通信的接口以及pigeon输出的配置。

这里@HostApi()标注了通信对象和接口的定义,后续需要在native侧注册该对象,在flutter侧通过该对象的实例来调用接口。 这里@FlutterApi()标注了通信对象和接口的定义,后续需要在flutter侧注册该对象,在native侧通过该对象的实例来调用接口。

configurePigeon为执行pigeon生产双端模板代码的输出配置(输出路径文件夹需先创建, 如 ios/Runner/)。

  • dartOut为dart侧输出位置
  • objcHeaderOut、objcSourceOut为iOS侧输出位置
  • prefix为插件默认的前缀
  • kotlinOut、kotlinOptions.package为Android侧输出位置和包名

之后我们只需要执行如下命令,就可以生成对应的代码到指定目录中。

flutter pub run pigeon --input pigeons/message.dart
  • --input为我们的输入文件

解决 Android native生成的代码Pigeon中的错误

// android/build.gradle
    android {
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }

        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
实现 flutter 与 native 互调(only Android端)
flutter调用native
android plugin

android/src/main/kotlin/com/zero/flutter_pigeon_plugin/FlutterPigeonPlugin.kt

class FlutterPigeonPlugin: FlutterPlugin, FlutterCallNativeApi {
  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    context = flutterPluginBinding.applicationContext

    // setup
    FlutterCallNativeApi.setUp(flutterPluginBinding.binaryMessenger, this)
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    FlutterCallNativeApi.setUp(binding.binaryMessenger, null)
  }

  // flutter call native
  override fun search(arg: SearchRequest): SearchReply {
    val reply = SearchReply(arg.query + "-nativeResult")

    // ------ native call flutter
    nativeApi.query(arg){
      Toast.makeText(context, reply.result, Toast.LENGTH_SHORT).show()
    }
    // -------

    // native reply flutter
    return reply
  }

}
example demo使用

Android侧

example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java,build时自动生成

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new com.zero.flutter_pigeon_plugin.FlutterPigeonPlugin());
  }
}

example/android/app/src/main/kotlin/com/zero/flutter_pigeon_plugin_example/MainActivity.kt

class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        // notice : here will auto call
        // flutterEngine.getPlugins().add(new com.zero.flutter_pigeon_plugin.FlutterPigeonPlugin());
        super.configureFlutterEngine(flutterEngine)
    }
}

super.configureFlutterEngine(flutterEngine)中通过反射,调用了GeneratedPluginRegistrant.registerWith()

flutter 侧

example/lib/main.dart

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';

  Future<void> getNativeResult() async{
    FlutterCallNativeApi api = FlutterCallNativeApi();
    SearchRequest request = SearchRequest()..query = "Zero";
    SearchReply reply = await api.search(request);
    setState(() {
      _platformVersion = reply.result;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(

          child: Column(

            children: [
              Text('Running on: $_platformVersion\n'),
              MaterialButton(
                  height: 40,
                  color: Colors.blue,
                  textColor: Colors.white,
                  elevation: 5,
                  splashColor: Colors.teal,
                  padding: EdgeInsets.all(8),
                  child: Text("点击调用 native"),
                  onPressed: ()=>{
                    getNativeResult()
                  })
            ],
          ),
        ),
      ),
    );
  }
}
native 调用 flutter
android plugin

android/src/main/kotlin/com/zero/flutter_pigeon_plugin/FlutterPigeonPlugin.kt

class FlutterPigeonPlugin: FlutterPlugin, FlutterCallNativeApi {

  lateinit var nativeApi : NativeCallFlutterApi
  lateinit var context: Context

  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    context = flutterPluginBinding.applicationContext

    // setup
    FlutterCallNativeApi.setup(flutterPluginBinding.binaryMessenger, this)

    nativeApi = NativeCallFlutterApi(flutterPluginBinding.binaryMessenger)
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    FlutterCallNativeApi.setup(binding.binaryMessenger, null)
  }


  // flutter call native
  override fun search(arg: SearchRequest): SearchReply {
    val reply = SearchReply.Builder().setResult(arg.query + "-nativeResult").build()


    // ------ native call flutter
    nativeApi.query(arg, object : NativeCallFlutterApi.Reply<SearchReply>{
      override fun reply(reply: SearchReply) {
        // flutter reply
        Toast.makeText(context, reply.result, Toast.LENGTH_SHORT).show()
      }
    })
    // -------

    // native reply flutter
    return reply
  }

}
iOS plugin

ios/Classes/FlutterPigeonPlugin.h

#import <Flutter/Flutter.h>

#import "Pigeon.h"

@interface FlutterPigeonPlugin : NSObject<FlutterPlugin>

@end


@interface FlutterCallNativeApi : NSObject<FLTFlutterCallNativeApi>

- (void)setupFLTNativeCallFlutterApiWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;

@end

ios/Classes/FlutterPigeonPlugin.m

#import "FlutterPigeonPlugin.h"
#if __has_include(<flutter_pigeon_plugin/flutter_pigeon_plugin-Swift.h>)
#import <flutter_pigeon_plugin/flutter_pigeon_plugin-Swift.h>
#else
#import "flutter_pigeon_plugin-Swift.h"
#endif

#import <UIKit/UIKit.h>

@implementation FlutterPigeonPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  [SwiftFlutterPigeonPlugin registerWithRegistrar:registrar];



  FlutterCallNativeApi *api = [[FlutterCallNativeApi alloc] init];

  [api setupFLTNativeCallFlutterApiWithBinaryMessenger:[registrar messenger]];

  FLTFlutterCallNativeApiSetup([registrar messenger], api);

}

- (BOOL)application:(UIApplication*)application
    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
  NSLog(@"didFinishLaunchingWithOptions");
  return YES;
}

@end



@interface FlutterCallNativeApi ()

@property(nonatomic, strong) FLTNativeCallFlutterApi *nativeCallFlutterApi;

@end


@implementation  FlutterCallNativeApi

- (nullable FLTSearchReply *)searchRequest:(FLTSearchRequest *)request error:(FlutterError *_Nullable *_Nonnull)error
{
        NSString *result = @"flutter call iOS ";
        FLTSearchReply *reply = [FLTSearchReply makeWithResult:result];


        [self.nativeCallFlutterApi  queryRequest:[FLTSearchRequest makeWithQuery:@"iOS call flutter"] completion:^(FLTSearchReply *_Nullable reply, FlutterError * _Nullable error) {


                NSLog(@"queryRequest-> queryRequest : %@", reply.result);

        }];

        return reply;


}

- (void)setupFLTNativeCallFlutterApiWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
{
        self.nativeCallFlutterApi = [[FLTNativeCallFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
}

@end
example demo使用

flutter 侧

example/lib/main.dart

class NativeCallFlutterApiImpl extends NativeCallFlutterApi{
  @override
  SearchReply query(SearchRequest arg) {
    SearchReply reply = SearchReply(result: arg.query + "-flutterResult");
    return reply;
  }

}


class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  
  @override
  void initState() {
    super.initState();

    NativeCallFlutterApi.setup(NativeCallFlutterApiImpl());
  }

}

参考

  1. Pigeon- github message

  2. pigeon_plugin_example

  3. Pigeon- Flutter多端接口一致性以及规范化管理实践