Implementação de canal de método em dispositivos móveis e desktop
Implementação de canal de método em dispositivos móveis e desktop (Method Channel)
Android + IOS + MacOS + Windows + Linux
O canal de método permite que o flutter se comunique com a plataforma nativa. A mensagem do canal é envolta em binário e enviada com FIFO. Muitos pacotes usaram este método para implementar os recursos e não é raro que projetos exijam integração nativa. Por curiosidade, pesquisei no flutter.dev, ele forneceu os exemplos de Android, IOS e Windows, mas parece não ter o exemplo de MacOS e Linux. Este artigo resumiu a implementação da configuração do canal do método Android (Kotlin), IOS (Swift), MacOS (Swift), Windows (C++) e Linux (C) no projeto principal (NÃO o plugin flutter).
Conteudo
Flutter (Dart)
A primeira coisa é definir o nome do canal. Posteriormente no lado nativo, o nome do canal será utilizado para identificar se é o mesmo canal. Da mesma forma, o nome do método definido em _platform.invokeMethod
também será usado para identificar qual ação ele está chamando. Como a comunicação do canal é assíncrona, é necessário await
a resposta posteriormente retornada da plataforma nativa. Aqui está um estado simples de widget com estado que possui apenas um botão para acionar a chamada.
class _MyHomePageState extends State<MyHomePage> { /// TODO:[Flutter][1] Configure o MethodChannel final MethodChannel _platform = const MethodChannel('cbl.tool.flutter_platform_channel'); @override Widget build(BuildContext context) { return Scaffold( body: Builder(builder: (context) { return Center( child: ElevatedButton( child: Text('Diga oi para ${Platform.operatingSystem}!'), onPressed: () async { /// TODO:[Flutter][2] Enviar chamada para a plataforma String msg = await _platform.invokeMethod('shakeHand'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), ), ); } }, ), ); }), ); } }
Android (Kotlin)
No Android, o que precisamos fazer é substituir o configureFlutterEngine
e usar o FlutterEngine para criar um MethodChannel
pelo mensageiro binário e pelo nome do canal. O setMethodCallHandler
retornará a chamada se houver um método chamado do lado do flutter. Quando o nome do método é shakeHand
, o canal posteriormente cumprimentará o lado flutter.
class MainActivity: FlutterActivity() { private val channel = "cbl.tool.flutter_platform_channel" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // TODO:[Android] [1] substitua configureFlutterEngine e crie MethodChannel MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler { call, result -> if (call.method == "shakeHand") { result.success("Olá do Android!"); }else{ result.notImplemented(); } } } }
IOS(Swift)
Para IOS, o binaryMessenger
é recuperado do FlutterViewController, que será bind no rootViewController
durante a inicialização do aplicativo.
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // TODO:[IOS][1]Obtenha o controlador de visualização raiz flutter para mensageiro binário let controller: FlutterViewController = window?.rootViewController as! FlutterViewController // TODO:[IOS][2] Defina o canal de flutter let flutterChannel = FlutterMethodChannel(name: "cbl.tool.flutter_platform_channel", binaryMessenger: controller.binaryMessenger) // TODO:[IOS][3] Definir o manipulador de chamada do método de canal flutterChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in if (call.method == "shakeHand") { result(String("Olá do IOS!")) } else { result(FlutterMethodNotImplemented) } }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
Mac OS (Swift)
Semelhante ao IOS, o MacOS binaryMessenger
é recuperado do FlutterViewController. A configuração é basicamente igual à do IOS.
class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) // TODO:[Mac][1] Defina o canal de flutter let flutterChannel = FlutterMethodChannel(name: "cbl.tool.flutter_platform_channel", binaryMessenger: flutterViewController.engine.binaryMessenger) // TODO:[Mac][2] Definir o manipulador de chamada do método de canal flutterChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in if (call.method == "shakeHand") { result(String("Olá do MacOS!")) } else { result(FlutterMethodNotImplemented) } }) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } }
Windows(C++)
Basicamente, semelhante ao MacOS, usando o flutterViewController para obter o mensageiro binário. Esteja ciente de importar os arquivos de cabeçalho.
#include "flutter_window.h" #include <flutter/event_channel.h> #include <flutter/event_sink.h> #include <flutter/event_stream_handler_functions.h> #include <flutter/method_channel.h> #include <flutter/standard_method_codec.h> #include <optional> #include "flutter/generated_plugin_registrant.h" bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique<flutter::FlutterViewController>( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); // TODO:[Windows] [1] use flutter _controller, pegue o motor e depois o messenger flutter::MethodChannel<> channel( flutter_controller_->engine()->messenger(), "cbl.tool.flutter_platform_channel", &flutter::StandardMethodCodec::GetInstance()); channel.SetMethodCallHandler( [](const flutter::MethodCall<>& call, std::unique_ptr<flutter::MethodResult<>> result) { if (call.method_name() == "shakeHand") { result->Success(flutter::EncodableValue("Olá do Windows!")); } else { result->NotImplemented(); } }); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; }
Linux(C)
Para Linux, use o mecanismo flutter para obter o mensageiro binário e, em seguida, crie um novo canal de método. O method_call_cb
é para lidar com o retorno de chamada do método.
// TODO: [Linux][5] adicione manipulação a cada nome de método static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { g_autoptr(FlMethodResponse) response = nullptr; const gchar* method = fl_method_call_get_name(method_call); if (strcmp(method, "shakeHand") == 0) { response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_string("Olá do Linux!"))); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } fl_method_call_respond(method_call, response, nullptr); } // Implementa GApplication::activate. static void my_application_activate(GApplication* application) { ... fl_register_plugins(FL_PLUGIN_REGISTRY(view)); // TODO: [Linux][1] Obtenha o motor da visualização FlEngine *engine = fl_view_get_engine(view); // TODO: [Linux][2] Obtenha o mensageiro binário g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine); // TODO: [Linux][3] Definir canal g_autoptr(FlMethodChannel) channel = fl_method_channel_new(messenger, "cbl.tool.flutter_platform_channel", // this is our channel name FL_METHOD_CODEC(fl_standard_method_codec_new())); // TODO: [Linux][4] Definir manipulador de chamadas de canal fl_method_channel_set_method_call_handler(channel, method_call_cb, g_object_ref(view), g_object_unref); gtk_widget_grab_focus(GTK_WIDGET(view)); }
Mapeamento de valor
Já o canal do método usa o codec de mensagem padrão que suporta serializar e desserializar mensagens binárias para diferentes tipos. Esta é a tabela para o mapeamento de tipos:
Dart | Kotlin | Java | Swift | Objective-C | C++ | C |
null | null | null | nil | nil (NSNull when nested) | EncodableValue() | FlValue() |
bool | Boolean | java.lang.Boolean | NSNumber(value: Bool) | NSNumber numberWithBool: | EncodableValue(bool) | FlValue(bool) |
int | Int | java.lang.Integer | NSNumber(value: Int32) | NSNumber numberWithInt: | EncodableValue(int32_t) | FlValue(int62_t) |
int, if 32 bits not enough | Long | java.lang.Long | NSNumber(value: Int) | NSNumber numberWithLong: | EncodableValue(int64_t) | FlValue(double) |
double | Double | java.lang.Double | NSNumber(value: Double) | NSNumber numberWithDouble: | EncodableValue(double) | FlValue(gchar*) |
String | String | java.lang.String | String | NSString | EncodableValue(std::string) | FlValue(uint8_t*) |
Uint8List | ByteArray | byte[] | FlutterStandardTypedData(bytes: Data) | FlutterStandardTypedData typedDataWithBytes: | EncodableValue(std::vector) | FlValue(int32_t*) |
Int32List | IntArray | int[] | FlutterStandardTypedData(int32: Data) | FlutterStandardTypedData typedDataWithInt32: | EncodableValue(std::vector) | FlValue(int64_t*) |
Int64List | LongArray | long[] | FlutterStandardTypedData(int64: Data) | FlutterStandardTypedData typedDataWithInt64: | EncodableValue(std::vector) | FlValue(int64_t*) |
Float32List | FloatArray | float[] | FlutterStandardTypedData(float32: Data) | FlutterStandardTypedData typedDataWithFloat32: | EncodableValue(std::vector) | FlValue(float*) |
Float64List | DoubleArray | double[] | FlutterStandardTypedData(float64: Data) | FlutterStandardTypedData typedDataWithFloat64: | EncodableValue(std::vector) | FlValue(double*) |
List | List | java.util.ArrayList | Array | NSArray | EncodableValue(std::vector) | FlValue(FlValue) |
Map | HashMap | java.util.HashMap | Dictionary | NSArray | EncodableValue(std::map) | FlValue(FlValue, FlValue) |