
Всем привет! Это Мурат Насиров, 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:
-
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/
Добавить комментарий