Implementação de canal de método em dispositivos móveis e desktop

Tempo de leitura: 4 minutes

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).

 

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:

DartKotlinJavaSwiftObjective-CC++C
nullnullnullnilnil (NSNull when nested)EncodableValue()FlValue()
boolBooleanjava.lang.BooleanNSNumber(value: Bool)NSNumber numberWithBool:EncodableValue(bool)FlValue(bool)
intIntjava.lang.IntegerNSNumber(value: Int32)NSNumber numberWithInt:EncodableValue(int32_t)FlValue(int62_t)
int, if 32 bits not enoughLongjava.lang.LongNSNumber(value: Int)NSNumber numberWithLong:EncodableValue(int64_t)FlValue(double)
doubleDoublejava.lang.DoubleNSNumber(value: Double)NSNumber numberWithDouble:EncodableValue(double)FlValue(gchar*)
StringStringjava.lang.StringStringNSStringEncodableValue(std::string)FlValue(uint8_t*)
Uint8ListByteArraybyte[]FlutterStandardTypedData(bytes: Data)FlutterStandardTypedData typedDataWithBytes:EncodableValue(std::vector)FlValue(int32_t*)
Int32ListIntArrayint[]FlutterStandardTypedData(int32: Data)FlutterStandardTypedData typedDataWithInt32:EncodableValue(std::vector)FlValue(int64_t*)
Int64ListLongArraylong[]FlutterStandardTypedData(int64: Data)FlutterStandardTypedData typedDataWithInt64:EncodableValue(std::vector)FlValue(int64_t*)
Float32ListFloatArrayfloat[]FlutterStandardTypedData(float32: Data)FlutterStandardTypedData typedDataWithFloat32:EncodableValue(std::vector)FlValue(float*)
Float64ListDoubleArraydouble[]FlutterStandardTypedData(float64: Data)FlutterStandardTypedData typedDataWithFloat64:EncodableValue(std::vector)FlValue(double*)
ListListjava.util.ArrayListArrayNSArrayEncodableValue(std::vector)FlValue(FlValue)
MapHashMapjava.util.HashMapDictionaryNSArrayEncodableValue(std::map)FlValue(FlValue, FlValue)