Canaux de plateforme Flutter pour Linux

introduction

Cet article est destiné à ceux qui utilisent les canaux de plate-forme pour Linux avec Flutter. J'écrirai comment intégrer Dart à la plateforme Linux. Je n'écrirai pas la procédure de création de l'environnement de développement Flutter, la procédure de publication sous forme de licence ou de package, etc.

La plate-forme Linux de Flutter est actuellement pour Desktop, Flutter for Desktop est traité comme une version alpha au moment de la rédaction de cet article (2020/11/3). Veuillez noter que le contenu décrit dans cet article, en particulier sous linux /, peut avoir changé de manière significative à l'avenir.

En raison de telles circonstances, j'avais l'intention d'écrire en pensant à «comment savoir comment faire». Si vous remarquez des erreurs ou des pièces obsolètes, nous vous serions reconnaissants de bien vouloir les signaler.

Nous avons confirmé dans l'environnement suivant.

$ uname -a
Linux chama 5.5.8 #1 SMP Sat Mar 7 22:29:22 JST 2020 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
$ flutter --version
Flutter 1.24.0-7.0.pre.42 • channel master • https://github.com/flutter/flutter
Framework • revision c0ef94780c (2 days ago) • 2020-10-31 03:12:27 -0700
Engine • revision 3460519398
Tools • Dart 2.12.0 (build 2.12.0-3.0.dev)

Présentation des canaux de plate-forme

Lors de l'appel d'API spécifiques à la plate-forme dans Flutter, nous utilisons un mécanisme appelé Platform Channels.

Les canaux de plate-forme vous permettent de travailler de manière flexible avec une variété de plates-formes Il dispose d'un mécanisme de transmission de messages asynchrone simple.

S'il est uniquement asynchrone, le code du côté qui utilise l'API semble difficile à comprendre, Dart a async / await, ce qui le rend flexible et simple à implémenter sans compromettre la lisibilité.

Platform Channels fournit les trois API suivantes en fonction de l'objectif. Cet article est facile à comprendre pour savoir comment les utiliser correctement et comment ils sont utilisés.

Commencez par créer un modèle

Créez une commande flutter create pour créer un projet. À ce stade, vous pouvez créer un projet à partir du modèle pour le développement du plugin en spécifiant --template plugin. Pour --org, spécifiez l'organisation Notation inversée du nom de domaine. Créez un plugin pour la plateforme Linux avec le nom "platform_proxy" avec la commande suivante.

flutter create --platforms linux --template plugin --org xyz.takeoverjp.example platform_proxy

Ce n'est pas le point principal, mais il est recommandé de git commit à ce stade afin que vous puissiez voir ce que vous avez falsifié.

Essayez d'exécuter l'exemple d'application

Dans le platform_proxy généré, il y a aussi un exemple d'application appelé exemple. Lorsque vous exécutez l'exemple d'application, l'écran suivant s'affiche. Si vous avez touché Linux, cela peut provenir d'une chaîne, mais c'est un exemple qui produit le résultat de uname -v.

Screenshot from 2020-11-03 02-21-48.png

Lire le code généré

Vous pouvez comprendre le flux d'appels en prêtant attention aux trois suivants du code généré.

De plus, lors du développement de l'API Plugin, nous modifierons également le test unitaire, nous allons donc le présenter ici également.

Appelant d'API de plug-in

platform_proxy/example/lib/main.dart


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

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

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      platformVersion = await PlatformProxy.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('Running on: $_platformVersion\n'),
        ),
      ),
    );
  }
}

PlatformProxy.platformVersion est l'API ajoutée par modèle. Comme vous pouvez le voir dans les commentaires, PlatformChannel est un message asynchrone. Puisque le MethodChannel utilisé cette fois est également un appel asynchrone, async / await est utilisé. Pour async / await de Dart, cet article est facile à comprendre. En termes simples, c'est la syntaxe pour recevoir une réponse à un appel asynchrone dans la prochaine boucle d'événements.

Implémentation de l'API du plugin (partie Dart)

platform_proxy/lib/platform_proxy.dart


class PlatformProxy {
  static const MethodChannel _channel =
      const MethodChannel('platform_proxy');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

Une implémentation de PlatformProxy # platformVersion. Il crée un MethodChannel pour le platform_proxy et appelle le MethodChannel # invokeMethod.

Ici, Si les noms de canaux se chevauchent, le gestionnaire sera écrasé et la communication ne sera pas possible correctement. Je n'ai pas pu le trouver à la tête de la famille, mais dans cette page (nom de domaine) / (nom du plugin) / (nom du canal) Est recommandé. Dans le modèle, comme mentionné ci-dessus, MethodChannel est généré avec le nom du canal direct, N'oubliez pas de le changer, en particulier pour les plugins, car le risque de conflits de noms est élevé. Par exemple, dans Flutter official file_chooser plugin, comme flutter / filechooser est.

MethodChannel # invokeMethod Transmettez le nom de la méthode et les arguments que vous souhaitez appeler et appelez-les de manière asynchrone. De plus, puisque la réponse est Future, vous pouvez attendre le résultat avec await.

Au fait, le nom de la méthode passé à invokeMethod n'est qu'une chaîne. Même si l'appelant saisit le nom de la méthode, cela n'entraînera pas une erreur de compilation mais une exception d'exécution. Même si le type ou le nom de propriété est incorrect, il ne peut pas être détecté au moment de la compilation. Il est de la responsabilité de cette couche de rendre l'API du plugin plus lisible tout en empêchant une telle utilisation abusive.

Implémentation d'API du plugin (partie native)

platform_proxy/linux/platform_proxy_plugin.cc


static void platform_proxy_plugin_handle_method_call(
    PlatformProxyPlugin* self,
    FlMethodCall* method_call) {
  g_autoptr(FlMethodResponse) response = nullptr;

  const gchar* method = fl_method_call_get_name(method_call);

  if (strcmp(method, "getPlatformVersion") == 0) {
    struct utsname uname_data = {};
    uname(&uname_data);
    g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
    g_autoptr(FlValue) result = fl_value_new_string(version);
    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  fl_method_call_respond(method_call, response, nullptr);
}
...
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
                           gpointer user_data) {
  PlatformProxyPlugin* plugin = PLATFORM_PROXY_PLUGIN(user_data);
  platform_proxy_plugin_handle_method_call(plugin, method_call);
}

void platform_proxy_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
  PlatformProxyPlugin* plugin = PLATFORM_PROXY_PLUGIN(
      g_object_new(platform_proxy_plugin_get_type(), nullptr));

  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel =
      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
                            "platform_proxy",
                            FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_cb,
                                            g_object_ref(plugin),
                                            g_object_unref);

  g_object_unref(plugin);
}

Flutter for Desktop Linux implémente des boucles d'événements à l'aide de GLib. Je n'étais pas habitué à GLib, donc j'ai été décontenancé au début, Les fonctions GLib (g _ ***) sont presque du code pour la gestion des objets, alors ne vous inquiétez pas. Il sera plus facile à comprendre si vous faites attention à la fonction de scintillement (fl _ ***).

Vers le FlMethodChannel généré par fl_method_channel_new lors de l'enregistrement du plugin La fonction de rappel est enregistrée avec fl_method_channel_set_method_call_handler.

Lorsque l'API cible est appelée côté Dart, le Flutter Engine appelle method_call_cb et Appelez platform_proxy_plugin_handle_method_call avec les arguments convertis de manière appropriée.

Ensuite, récupérez le nom de la méthode avec fl_method_call_get_name et effectuez le traitement correspondant. Dans ce getPlatformVersion,uname (2)est appelé. Définissez le résultat comme une valeur de retour avec FL_METHOD_RESPONSE (fl_method_success_response_new (result)) Nous renvoyons une réponse au Flutter Engine en appelant fl_method_call_respond.

De plus, la communication des canaux de plate-forme est effectuée par le thread principal est requise. Si vous avez calculé le résultat dans un autre thread, utilisez g_main_context_invoke etc. pour déplacer le contexte vers le thread principal, puis renvoyez la réponse.

Test unitaire de l'API Plugin

Vous pouvez exécuter un test unitaire de l'API Plugin en exécutant flutter test. MethodChannel a une méthode pour DI appelée setMockMethodCallHandler. Un test unitaire peut être effectué avec la partie native séparée. Au contraire, il ne s'agit pas d'un test d'implémentation de la partie Native, il faut donc le faire séparément.

test/plugin_method_test.dart


void main() {
  const MethodChannel channel = MethodChannel('platform_proxy');

  TestWidgetsFlutterBinding.ensureInitialized();

  setUp(() {
    channel.setMockMethodCallHandler((MethodCall methodCall) async {
      return '42';
    });
  });

  tearDown(() {
    channel.setMockMethodCallHandler(null);
  });

  test('getPlatformVersion', () async {
    expect(await PlatformProxy.platformVersion, '42');
  });
}

Ajouter une API

Lors de l'appel de fonctions de plate-forme à partir de Dart dans BasicMessageChannel Vous pouvez ajouter une API en ajoutant N aux trois codes ci-dessus.

Si vous voulez retourner un autre type ou prendre un argument exemple et [commentaire d'en-tête](https://github.com/flutter/engine/tree/master/shell/platform/linux/public/ flutter_linux) sera utile.

Veuillez également vous référer aux exemples de projets suivants pour d'autres variantes. takeoverjp/flutter-linux-plugin-example: A plugin example for platform channels on Linux desktop.

[Référence] À propos du package Pigeon généré automatiquement

D'ailleurs, dans la méthode ci-dessus, le nom de la méthode est traité comme une simple chaîne de caractères, Le type de l'argument et de la valeur de retour pour chaque méthode est un gentleman's agreement. Si cela diffère entre la partie Dart et la partie native, ce sera une exception ou le pire comportement indéfini. Si vous y accédez toujours via le plugin et que vous le testez correctement, vous pouvez réduire la possibilité de problèmes réels, mais vous avez toujours des préoccupations.

En guise de contre-mesure, un package pour la génération automatique appelé pigeon est préparé. A partir d'un fichier Pigeon écrit dans un sous-ensemble de Dart, nous allons générer le code pour communiquer entre la plateforme et Dart comme décrit ci-dessus.

Cependant, le bureau n'est pas encore pris en charge à la version 0.1.14 au moment de la rédaction de l'article (2020/11/3). Il est toujours disponible en tant qu'outil pour générer une implémentation de la partie Dart. C'est un élément important pour faire correspondre le symbole avec la partie native, donc ce sera un peu décevant, mais Je laisserai la méthode pour référence.

Le flux de génération automatique est le suivant. Fichier de définition de l'API et [Generated Dart code](https://github.com Si vous regardez également /takeoverjp/flutter-linux-plugin-example/blob/master/lib/pigeon_platform_proxy.dart), il peut être plus facile de saisir l'image.

  1. Ajoutez pigeon à dev_dependencies dans pubspec.yaml
  2. Préparez un fichier de fléchettes qui définit la déclaration du prototype d'API et les types requis.
  3. Générez la pièce Dart avec la commande suivante
flutter pub run pigeon --input pigeons/platform_proxy_message.dart --dart_out lib/pigeon_platform_proxy.dart

Après cela, la partie native sera implémentée en fonction du fichier de fléchettes généré. Étant donné que le fichier de fléchettes généré utilise BasicMessageChannel, il est également nécessaire de faire correspondre la partie native.

En tant que restriction de l'API créée par Pigeon (0.1.14), l'argument et la valeur de retour doivent être la classe définie dans un ou moins de fichiers de définition d'API. Autrement dit, il ne prend pas en charge l'acceptation des entiers comme arguments, l'acceptation de plusieurs valeurs comme arguments ou le renvoi des entiers. En conséquence, cela passe toujours par la classe, donc le passeur et le receveur sont un peu redondants. En outre, il ne semblait pas encore prendre en charge les notifications d'événements et les appels de méthode du côté de la plate-forme.

en conclusion

J'ai essayé de voir comment utiliser les canaux de plate-forme dans Linux Desktop en fonction du code généré en tant que modèle de plugin. Les canaux de la plate-forme eux-mêmes sont simples, mais c'est assez grande impression car il est nécessaire d'implémenter à la fois la partie Dart et la partie native.

Pour le traitement qui gère de grandes quantités de données, les canaux de plate-forme qui exécutent la sérialisation sont désavantageux, donc je pense qu'il est préférable d'utiliser ffi.

D'autre part, si vous voulez faire IPC et que le partenaire de communication prend en charge socket / dbus / grpc de domaine unix, etc. En premier lieu, vous pourrez peut-être atteindre votre objectif en utilisant dart: io / dbus.dart / grpc-dart directement à partir de Dart.

Dans cet esprit, les canaux de plate-forme sont nécessaires pour utiliser l'API java / kotlin / Obj-C / swift platfrom pour Android / iOS, Si vous ne pensez qu'à Linux, cela ne joue peut-être pas beaucoup.

C'est à cette époque que j'ai dû bien connaître chaque méthode afin de pouvoir choisir la méthode qui convient à ma situation. .. ..

référence

Recommended Posts

Canaux de plateforme Flutter pour Linux
pyenv pour linux
[Pour mémoire] Linux Partie 2
À quoi sert Linux?
Votre propre Koredake est une commande Linux
Compilation du noyau Linux pour DE10nano
Platform Channel VS FFI (Foreign Function Interface) sur Flutter sous Linux
Distribution Linux recommandée pour les débutants
[Linux] [Réglage initial] [Flutter] Résumé
Dictionnaire de commande Linux (pour moi-même)
Mémorandum de commande Linux [pour les débutants]
Raccourci Linux pratique (pour les débutants)
Touche de raccourci pratique pour les commandes Linux! !! !!
Commandes Linux fréquemment utilisées (pour les débutants)
[À voir pour les débutants] Bases de Linux
Procédure d'installation de Teamviewer pour Linux (CentOS)
pykintone sur le sous-système Windows pour Linux
Formation de base Linux pour l'ingénieur front-end
Utiliser Azure AD pour l'authentification Linux