Создаем federated plugin для Flutter-проекта

от автора

Всем привет! Это Мурат Насиров, Flutter-разработчик в Friflex. Мы разрабатываем высоконагруженные мобильные приложения для бизнеса и специализируемся на Flutter. В этой статье я рассказываю о том, как создать federated plugin для Flutter-проектов.

В мае 2022 года на Google I/O был представлен урок по созданию federated plugin в Flutter. Federated plugin — это способ разделения функционала в рамках одного плагина на разные платформы. Он позволяет сегрегировать функционал на зоны ответственности для каждой из платформ. 

К примеру, если мы создаем плагин для работы с bluetooth, тогда нужно будет создавать пакеты отдельно для каждой платформы, то есть: flutter_bluetooth (как пакет flutter), flutter_bluetooth_android, flutter_bluetooth_ios и flutter_bluetooth_platform_interface (интерфейс для работы с платформами). 

Создавая federated plugins для всех платформ, разработчики могут использовать только те из них, которые необходимы. 

Среди плюсов также можно отметить то, что специалисты, разрабатывающие плагин в конкретных платформах, не зависят от кода в других платформах.

Структура взаимодействия пакетов внутри federated plugin:

Структура взаимодействия пакетов внутри federated plugin

Структура взаимодействия пакетов внутри federated plugin
  • app-facing package — пакет, в котором описывается API на языке Dart для взаимодействия сplatform interface package. То есть это тот самый flutter_bluetooth, который мы прописываем в pubspec.yaml, когда хотим получить функционал библиотеки со всеми платформенными плагинами вместе. Таким образом, этот app-facing package зависит от platform interface package и platform packages;

  • platform interface package — пакет-связка между app-facing package и platform packages. В пакете описывается интерфейс плагина. Как и при создании обычного плагина, объявляются функции, которые будут использованы для вызова платформенного кода. В этом пакете применяется зависимость plugin_platform_interface, помогающая описать интерфейс, который будет использоваться в platform packages;

  • platform packages — пакеты, представляющие платформенную реализацию методов из абстракции platform interface package

Независимость каждого компонента упрощает настройку и отладку кода. Разработчикам, работающим с Android и iOS, не нужно знать о прогрессе или особенностях реализации платформы друг друга. Нужно лишь применить методы из интерфейса, которые описаны в platform interface package.

Создание federated plugin

Идея создавать независимые плагины для каждой из платформ появилась еще в 2019 году Однако по сей день можно лишь использовать команду для создания стандартного плагина:

 flutter create --org plugin --template=plugin --platforms=android,linux platform_info

Со временем сообщество предложило свои реализации этого подхода, из которых рабочая, как мне известно, только Very Good Flutter Plugin. Это решение со своими нюансами, нужно убирать немало лишнего и подпиливать до нужной кондиции. Поэтому, мне кажется, будет проще создать federated plugin самим.

Используем команду выше в удобной папке, где будет создан плагин. Так как в команде применяются платформы android и linux, всего понадобится создать четыре папки: platform_info, platform_info_android, platform_info_linux, platform_info_platform_interface

В папку platform_info_android перенесем папку android, в папку platform_info_linux — папку linux, а в папку platform_info — папку example. Во все четыре папки нужно скопировать папку lib из корня (и test, если нужны тесты), а также pubspec.yaml,analysis_options.yaml, .gitignore и README.md (по желанию). Из корня удаляем все, кроме четырех папок, папки .idea (если вы пользуетесь IntelliJ IDEA/Android Studio), .gitignore и README.md (по желанию). Должна получиться примерно такая структура:

Структура проектов после размещения файлов

Структура проектов после размещения файлов

Настройка platform interface

Теперь настроим структуру и код в каждой папке. Начнём с platform_info_platform_interface. В pubspec.yaml вставляем с заменой код:

name: platform_info_platform_interface description: A common platform interface for the platform_info plugin. version: 0.1.0 publish_to: none  environment:  sdk: ">=3.0.0 <4.0.0"  dependencies:  plugin_platform_interface: ^2.1.6

Из папки lib удаляем все, кроме platform_info_platform_interface.dart, в котором чуть меняем содержимое:

import 'package:plugin_platform_interface/plugin_platform_interface.dart';  abstract class PlatformInfoPlatform extends PlatformInterface {  PlatformInfoPlatform() : super(token: _token);   static final Object _token = Object();   static PlatformInfoPlatform _instance = _PlaceholderImplementation();   /// Стандартный [instance] текущего класса.  ///  /// По умолчанию [_PlaceholderImplementation].  static PlatformInfoPlatform get instance => _instance;   /// Имплементация текущего [instance] на определенной платформе.  ///  /// В коде платформы должна быть реализована функция, определяющая [instance].  static set instance(PlatformInfoPlatform instance) {    PlatformInterface.verifyToken(instance, _token);    _instance = instance;  }   Future<String?> getPlatformVersion() {    throw UnimplementedError('platformVersion() has not been implemented.');  } }  class _PlaceholderImplementation extends PlatformInfoPlatform {}

Настройка Android-платформы

Переходим к настройке platform_info_android. В pubspec.yaml меняем все на:

name: platform_info_android description: Android implementation of the platform_info plugin. version: 0.1.0 publish_to: none  environment:  sdk: ">=3.0.0 <4.0.0"  flutter:  plugin:    implements: platform_info    platforms:      android:        package: plugin.platform_info        pluginClass: PlatformInfoPlugin #Главный класс Android кода        dartPluginClass: PlatformInfoAndroid #Главный класс Dart кода  dependencies:  flutter:    sdk: flutter  platform_info_platform_interface:    path: ../platform_info_platform_interface  dev_dependencies:  flutter_lints: ^2.0.0

Удаляем все из platform_info_android/lib, создаем файл platform_info_android.dart и добавляем:

import 'package:flutter/services.dart'; import 'package:platform_info_platform_interface/platform_info_platform_interface.dart';  class PlatformInfoAndroid extends PlatformInfoPlatform {  static const MethodChannel _channel = MethodChannel('platform_info_android');   static void registerWith() {    PlatformInfoPlatform.instance = PlatformInfoAndroid();  }   @override  Future<String?> getPlatformVersion() {    return _channel.invokeMethod('getPlatformVersion');  } }

На стороне Android нужно просто поменять название платформенного канала. В platform_info_android/android/src/main/kotlin/plugin/platform_info/PlatformInfoPlugin.kt указываем с заменой:

channel = MethodChannel(flutterPluginBinding.binaryMessenger, "platform_info_android")

Настройка Linux-платформы

Порядок, теперь переходим к настройке platform_info_linux. В pubspec.yaml указываем:

name: platform_info_linux description: Linux implementation of the platform_info plugin. version: 0.1.0 publish_to: none  environment:  sdk: ">=3.0.0 <4.0.0"  flutter:  plugin:    implements: platform_info    platforms:      linux:        pluginClass: PlatformInfoPlugin #Главный класс Linux кода        dartPluginClass: PlatformInfoLinux #Главный класс Dart кода  dependencies:  flutter:    sdk: flutter  platform_info_platform_interface:    path: ../platform_info_platform_interface  dev_dependencies:  flutter_lints: ^2.0.0

Также опустошаем все из platform_info_linux/lib, создаем файл platform_info_linux.dart и добавляем:

import 'package:flutter/services.dart'; import 'package:platform_info_platform_interface/platform_info_platform_interface.dart';  class PlatformInfoLinux extends PlatformInfoPlatform {  static const MethodChannel _channel = MethodChannel('platform_info_linux');   static void registerWith() {    PlatformInfoPlatform.instance = PlatformInfoLinux();  }   @override  Future<String?> getPlatformVersion() {    return _channel.invokeMethod('getPlatformVersion');  } }

В platform_info/platform_info_linux/linux/platform_info_plugin.cc в методе platform_info_plugin_register_with_registrar также заменяем channel:

g_autoptr(FlMethodChannel) channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), "platform_info_linux", FL_METHOD_CODEC(codec));

Также нужно в platform_info/platform_info_linux/linux/include/platform_info_linux/ переместить файл platform_info_plugin.h, затем в platform_info/platform_info_linux/linux/platform_info_plugin.cc и platform_info/platform_info_linux/linux/platform_info_plugin_private.h заменить

#include "include/platform_info/platform_info_plugin.h"

на

#include "include/platform_info_linux/platform_info_plugin.h"

А в platform_info/platform_info_linux/linux/CMakeLists.txt заменить

# Project-level configuration. set(PROJECT_NAME "platform_info") project(${PROJECT_NAME} LANGUAGES CXX)  # This value is used when generating builds using this plugin, so it must # not be changed. set(PLUGIN_NAME "platform_info_plugin")

на

# Project-level configuration. set(PROJECT_NAME "platform_info_linux") project(${PROJECT_NAME} LANGUAGES CXX)  # This value is used when generating builds using this plugin, so it must # not be changed. set(PLUGIN_NAME "${PROJECT_NAME}_plugin")

Настройка Flutter-пакета для работы с платформами

Наконец, настроим пакет platform_info. Он будет подтягивать все платформенные реализации. В pubspec.yaml указываем:

name: platform_info description: Flutter package of the platform_info plugin. version: 0.1.0 publish_to: none  environment:  sdk: ">=3.0.0 <4.0.0"  flutter:  plugin:    platforms:      android:        default_package: platform_info_android      linux:        default_package: platform_info_linux  dependencies:  flutter:    sdk: flutter  platform_info_android:    path: ../platform_info_android  platform_info_linux:    path: ../platform_info_linux  platform_info_platform_interface:    path: ../platform_info_platform_interface  dev_dependencies:  flutter_lints: ^2.0.0

Из platform_info/lib также все удаляем и создаем файл platform_info.dart. А затем пишем:

import 'package:platform_info_platform_interface/platform_info_platform_interface.dart';  class PlatformInfo {  static PlatformInfoPlatform get _platform => PlatformInfoPlatform.instance;   static Future<String?> getPlatformVersion() async {    return _platform.getPlatformVersion();  } }

Настройка example

Немного меняем platform_info/platform_info/example/lib/main.dart:

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:platform_info/platform_info.dart';  void main() {  runApp(const MyApp()); }  class MyApp extends StatefulWidget {  const MyApp({super.key});   @override  State<MyApp> createState() => _MyAppState(); }  class _MyAppState extends State<MyApp> {  String _platformVersion = 'Неизвестно';   @override  void initState() {    super.initState();    initPlatformState();  }   /// Так как платформенные вызовы асинхронны, мы ожидаем получения информации.  Future<void> initPlatformState() async {    String platformVersion;    // Платформенный вызов может завершиться с ошибкой, поэтому здесь используется    // блок try/on PlatformException.    try {      platformVersion =          await PlatformInfo.getPlatformVersion() ?? 'Неизвестная версия платформы';    } on PlatformException {      platformVersion = 'Не удалось получить версию платформы.';    }       // Если виджет был удален из дерева во время выполнения асинхронной функции    // - выходим из нее.    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('Запущено на: $_platformVersion\n'),        ),      ),    );  } }

А из platform_info/platform_info/example/android/settings.gradle, platform_info/platform_info/example/android/app/build.gradle и platform_info/platform_info/example/android/build.gradle удаляем, если есть:

package platform_info.example.android.app

Теперь запускаем Android:

И попробуем запустить Linux:

Вот и все:) Это решение отлично подходит для работы с любой платформой. Разница лишь в специфике, как, например, с linux-частью. Проект шаблона доступен на GitHub. Делитесь впечатлениями об этом решении, что показалось сложным, где нужны уточнения. Спасибо, что дочитали!


ссылка на оригинал статьи https://habr.com/ru/articles/780956/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *