Реализации плагина с использованием Qt
Пример платформо-зависимого плагина, работающего с операционной системой Аврора через механизм Platform Channels с использованием сигналов и слотов Qt. Задача плагина — наблюдать за состоянием сетевого подключения с использованием механизма сигналов и слотов Qt.
Реализация плагина с использованием Platform Channels даёт максимальные возможности для создания плагина, а подключение Qt — широкий выбор готовых компонентов работающий на операционной системе Аврора. Но нужно учитывать, что реализация такого плагина требует дополнительных ресурсов. Т.е. реализация плагина без использования Qt, все же, является плюсом в пользу производительности.
Примечание:
Этот демонстрационный плагин доступен в репозитории Flutter в разделе examples/platform_channels_qt.
Создание приложения
Плагин с поддержкой Qt создаётся аналогично плагину
Platform Channels,
который является платформо-зависимым, то есть зависит от операционной системы.
Подробнее ознакомиться с типами плагинов можно в разделе Пакеты Flutter.
Platform Channels позволяет реализовать плагин, который обменивается данными через MethodChannel или EventChannel
с платформо-зависимой частью плагина из Dart-части плагина.
Platform Channels также даёт доступ к публичным методам Flutter Embedder.
Как показано в примере плагина FFI, методы Qt могут работать без каких-либо дополнений. Но для работы с сигналами и слотами требуются доработки, которые продемонстрируем в этом разделе.
Для генерации шаблона плагина можно выполнить следующую команду в терминале:
flutter-aurora create --template=plugin --platforms aurora --org=ru.aurora platform_channels_qt_demo
ru.aurora— имя организации (Organization name), участвует в формировании названия пакета;platform_channels_qt_demo— название плагина (Application Name), участвует в формировании названия пакета.
Данная команда генерирует базовый пример плагина Flutter с настроенным окружением для сборки под ОС Аврора. Структура файлов и каталогов проекта имеет следующий вид:
.
├── aurora
│ ├── include
│ │ └── platform_channels_qt_demo
│ │ ├── globals.h
│ │ └── platform_channels_qt_demo_plugin.h
│ ├── CMakeLists.txt
│ └── platform_channels_qt_demo_plugin.cpp
├── example
│ ├── aurora
│ │ ├── desktop
│ │ │ └── ru.aurora.platform_channels_qt_demo_example.desktop
│ │ ├── icons
│ │ │ ├── 108x108.png
│ │ │ ├── 128x128.png
│ │ │ ├── 172x172.png
│ │ │ └── 86x86.png
│ │ ├── rpm
│ │ │ └── ru.aurora.platform_channels_qt_demo_example.spec
│ │ ├── CMakeLists.txt
│ │ └── main.cpp
│ ├── integration_test
│ │ └── plugin_integration_test.dart
│ ├── lib
│ │ └── main.dart
│ ├── test
│ │ └── widget_test.dart
│ ├── analysis_options.yaml
│ ├── platform_channels_qt_demo_example.iml
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── README.md
├── lib
│ ├── platform_channels_qt_demo.dart
│ ├── platform_channels_qt_demo_method_channel.dart
│ └── platform_channels_qt_demo_platform_interface.dart
├── test
│ ├── platform_channels_qt_demo_method_channel_test.dart
│ └── platform_channels_qt_demo_test.dart
├── analysis_options.yaml
├── CHANGELOG.md
├── LICENSE
├── platform_channels_qt_demo.iml
├── pubspec.lock
├── pubspec.yaml
└── README.md
14 directories, 32 files
Данная структура является типичной для приложений на Flutter.
Исключением является директория <project>/aurora, в которой находится С++-код плагина
для взаимодействия с Flutter Embedder
и реализации общения с кодом плагина на Dart через Platform Channels:
CMakeLists.txt— сборка плагина реализована через CMake;globals.h— содержит определениеPLUGIN_EXPORT;platform_channels_demo_plugin.h— заголовочный файл реализации плагина;platform_channels_demo_plugin.cpp— реализация плагина.
В директории <project>/example/aurora находятся файлы, обеспечивающие работу демо-приложения плагина на платформе ОС Аврора:
CMakeLists.txt— приложение и плагины Flutter для ОС Аврора имеют платформенную часть на С++, а сборка реализована через CMake.desktop/ru.aurora.app_demo.desktop— файл с конфигурацией ярлыка приложения. В нём можно указать название приложения, требуемые права для приложения и некоторые другие настройки.icons/*.png— иконки приложения.main.cpp— точка входа в приложение для ОС Аврора. Это зачастую шаблонный код для запуска всех необходимых компонентов Flutter и приложения.rpm/ru.aurora.app_demo.spec— файл с конфигурацией установочного пакета, который утилита rpmbuild использует для сборки RPM-пакета.
Примечание:
Для предварительного знакомства с Flutter можно обратиться к документации Flutter и создать своё первое приложение, используя информацию из статьи "Write your first Flutter app".
Доработка С++-части
В примере плагина на Platform Channels была продемонстрирована передача данных
и использования языкового канала MethodChannel.
В этом плагине будет продемонстрирована работа с событийным каналом EventChannel,
на который можно будет подписаться из кода Dart и слушать события, отправляемые из С++-части плагина.
События будут приходить из сигналов Qt о состоянии сетевого подключения.
Для начала следует добавить зависимости, необходимые для получения информации о статусе сетевого
подключения, в файл CMakeLists.txt плагина, находящегося по пути <plugin>/aurora/CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME platform_channels_qt_demo)
set(PLUGIN_NAME platform_channels_qt_demo_platform_plugin)
project(${PROJECT_NAME} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-psabi")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
find_package(PkgConfig REQUIRED)
# Добавить зависимости Qt
find_package(Qt5 COMPONENTS Core Network REQUIRED)
pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder)
add_library(${PLUGIN_NAME} SHARED platform_channels_qt_demo_plugin.cpp)
# Активировать AUTOMOC
set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden AUTOMOC ON)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::FlutterEmbedder)
# Добавить зависимости Qt
target_link_libraries(${PLUGIN_NAME} PUBLIC Qt5::Core Qt5::Network)
target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# Для поддержки Qt нужно добавить в include moc
target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME})
target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL)
Это шаблонный код, генерируемый Flutter CLI.
Важным элементом в файле CMakeLists.txt являются прокомментированные строки, которые необходимо
добавить для поддержки сигналов и слотов Qt.
Далее следует доработать заголовочный файл С++-части плагина, находящийся по пути
<plugin>/aurora/include/platform_channels_qt_demo/platform_channels_qt_demo_plugin.h:
#ifndef FLUTTER_PLUGIN_PLATFORM_CHANNELS_QT_DEMO_PLUGIN_H
#define FLUTTER_PLUGIN_PLATFORM_CHANNELS_QT_DEMO_PLUGIN_H
#include <platform_channels_qt_demo/globals.h>
// Добавить зависимость Qt
#include <QNetworkConfigurationManager>
#include <flutter/plugin_registrar.h>
#include <flutter/encodable_value.h>
#include <flutter/standard_method_codec.h>
#include <flutter/event_channel.h>
#include <flutter/event_stream_handler_functions.h>
// Flutter типы Encodable
typedef flutter::EncodableValue EncodableValue;
typedef flutter::EncodableMap EncodableMap;
typedef flutter::EncodableList EncodableList;
// Flutter события
typedef flutter::EventChannel<EncodableValue> EventChannel;
typedef flutter::EventSink<EncodableValue> EventSink;
// Включить QObject
class PLUGIN_EXPORT PlatformChannelsQtDemoPlugin final
: public QObject
, public flutter::Plugin
{
Q_OBJECT
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar);
// Создать слот
public slots:
void onEventChannelSend();
private:
// Создает плагин, который взаимодействует по данному каналу.
PlatformChannelsQtDemoPlugin(
std::unique_ptr<EventChannel> eventChannel
);
// Метод регистрируют обработчики каналов
void RegisterStreamHandler();
// Другие методы
void onEventChannelEnable();
void onEventChannelDisable();
// Переменные Flutter
std::unique_ptr<EventChannel> m_eventChannel;
std::unique_ptr<EventSink> m_sink;
// Переменные Qt
bool m_state;
QNetworkConfigurationManager m_manager;
QMetaObject::Connection m_onlineStateChangedConnection;
};
#endif /* FLUTTER_PLUGIN_PLATFORM_CHANNELS_QT_DEMO_PLUGIN_H */
Здесь необходимо заменить созданный из шаблона MethodChannel на EventChannel, который позволит
реализовать Stream на стороне Dart.
Затем следует добавить методы для реакции на подпись к Stream Dart.
После необходимо подключить QObject к классу, добавив необходимые методы для работы с событиями
и Qt классы, которые позволят слушать сигналы об изменении состояния сети.
Теперь можно переходить к реализации методов, объявленных в заголовочном файле.
Для этого следует открыть и модифицировать файл по пути <plugin>/aurora/platform_channels_qt_demo_plugin.cpp:
#include <platform_channels_qt_demo/platform_channels_qt_demo_plugin.h>
namespace Channels {
constexpr auto Event = "platform_channels_qt_demo";
} // пространство имён Channels
void PlatformChannelsQtDemoPlugin::RegisterWithRegistrar(flutter::PluginRegistrar* registrar) {
// Создать EventChannel с помощью StandardMethodCodec
auto eventChannel = std::make_unique<EventChannel>(
registrar->messenger(), Channels::Event,
&flutter::StandardMethodCodec::GetInstance()
);
// Создать плагин
std::unique_ptr<PlatformChannelsQtDemoPlugin> plugin(
new PlatformChannelsQtDemoPlugin(std::move(eventChannel))
);
// Зарегистрировать плагин
registrar->AddPlugin(std::move(plugin));
}
PlatformChannelsQtDemoPlugin::PlatformChannelsQtDemoPlugin(
std::unique_ptr<EventChannel> eventChannel)
: m_eventChannel(std::move(eventChannel)), m_state(m_manager.isOnline()) {
// Создать StreamHandler
RegisterStreamHandler();
}
void PlatformChannelsQtDemoPlugin::RegisterStreamHandler() {
// Создать обработчик событий Platform Channels
auto handler = std::make_unique<flutter::StreamHandlerFunctions<EncodableValue>>(
[&](const EncodableValue*,
std::unique_ptr<flutter::EventSink<EncodableValue>>&& events
) -> std::unique_ptr<flutter::StreamHandlerError<EncodableValue>> {
m_sink = std::move(events);
onEventChannelEnable();
return nullptr;
},
[&](const EncodableValue*) -> std::unique_ptr<flutter::StreamHandlerError<EncodableValue>> {
onEventChannelDisable();
return nullptr;
}
);
// Зарегистрировать событие
m_eventChannel->SetStreamHandler(std::move(handler));
}
void PlatformChannelsQtDemoPlugin::onEventChannelEnable() {
// Отправить после запуска
m_sink->Success(m_state);
// Подключить соединение для прослушивания
m_onlineStateChangedConnection =
QObject::connect(&m_manager,
&QNetworkConfigurationManager::onlineStateChanged,
this,
&PlatformChannelsQtDemoPlugin::onEventChannelSend);
}
void PlatformChannelsQtDemoPlugin::onEventChannelDisable() {
// Отключить соединение для прослушивания
QObject::disconnect(m_onlineStateChangedConnection);
}
void PlatformChannelsQtDemoPlugin::onEventChannelSend() {
// Отправить состояние если были изменения
auto state = m_manager.isOnline();
if (state != m_state) {
m_state = state;
m_sink->Success(m_state);
}
}
// Добавить мок-файл
#include "moc_platform_channels_qt_demo_plugin.cpp"
В конце файла добавлен мок-файл в формате moc_<имя_плагина>.cpp:
#include "moc_platform_channels_qt_demo_plugin.cpp"
Он автоматически реализует необходимый функционал для работы Qt в плагине.
Доработка Dart-части
После реализации платформо-зависимого кода С++ нужно модифицировать шаблонный код плагина
для создания Stream в Dart.
Плагин Dart содержит 3 класса:
PlatformChannelsQtDemo— сам плагин, который подключается в приложение;PlatformChannelsQtDemoPlatform— интерфейс, который позволяет расширять плагин;MethodChannelPlatformChannelsQtDemo— реализация методов интерфейса плагина.
В Dart-части плагина необходимо изменить все файлы для реализации задачи плагина, то есть для
получения состояния статуса сетевого подключения.
Для этого в файле <plugin>/lib/platform_channels_qt_demo.dart следует изменить класс PlatformChannelsQtDemo,
удалив шаблонный код и добавив метод со Stream, который будет сообщать о смене состояния сетевого подключения:
class PlatformChannelsQtDemo {
Stream<bool?> stateNetworkConnect() {
return PlatformChannelsQtDemoPlatform.instance.stateNetworkConnect();
}
}
В файле <plugin>/lib/platform_channels_qt_demo_platform_interface.dart следует обновить методы
интерфейса:
/// ...
Stream<bool?> stateNetworkConnect() {
throw UnimplementedError('connectNetworkState() has not been implemented.');
}
В файле <plugin>/lib/platform_channels_qt_demo_method_channel.dart реализовать метод интерфейса:
import 'package:flutter/services.dart';
import 'platform_channels_qt_demo_platform_interface.dart';
// Ключ канала плагина
const channelEvent = "platform_channels_qt_demo";
/// Реализация [PlatformChannelsQtDemoPlatform], использующая каналы методов.
class MethodChannelPlatformChannelsQtDemo extends PlatformChannelsQtDemoPlatform {
/// Канал метода, используемый для взаимодействия с собственной платформой.
final eventChannel = const EventChannel(channelEvent);
/// Отображение состояния сети с помощью EventChannel
@override
Stream<bool?> stateNetworkConnect() {
return eventChannel.receiveBroadcastStream().map((event) => event as bool);
}
}
Доработка примера
Шаблон плагина Platform Channels, генерируемый Flutter CLI, имеет приложение-пример работы с плагином в директории
<project>/example.
Необходимо доработать его для отображения статуса сетевого подключения через плагин.
Так как плагин использует библиотеку Qt Network,
её следует добавить в зависимости приложения.
Для этого в файл <project>/example/aurora/rpm/ru.aurora.platform_channels_qt_demo_example.spec
нужно добавить:
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Network)
Для получения статуса сетевого подключения приложению требуется разрешение Internet,
которое нужно добавить в файл <project>/example/aurora/desktop/ru.aurora.platform_channels_qt_demo_example.desktop:
Permissions=Internet
Активировать сигналы и слоты Qt можно, подключив доступный функционал Flutter Embedder к приложению.
Для этого необходимо открыть файл <project>/example/aurora/main.cpp и обновить его следующим образом:
#include <flutter/flutter_aurora.h>
#include <flutter/flutter_compatibility_qt.h> // <- Подключение Qt
#include "generated_plugin_registrant.h"
int main(int argc, char *argv[]) {
aurora::Initialize(argc, argv);
aurora::EnableQtCompatibility(); // <- Подключение Qt
aurora::RegisterPlugins();
aurora::Launch();
return 0;
}
Для повышения читаемости кода, систематизации и упрощения написания приложений-примеров
был разработан плагин internal_aurora.
Добавить в зависимость плагин internal_aurora в pubspec.yaml можно следующим образом:
dependencies:
internal_aurora:
git:
url: https://developer.auroraos.ru/git/flutter/flutter-community-plugins/internal_aurora.git
Следует обновить зависимости в директории example:
cd example
flutter-aurora pub get
Далее следует доработать приложение, которое будет использовать плагин platform_channels_qt_demo
и демонстрировать статус сетевого подключения:
import 'package:flutter/material.dart';
import 'package:internal_aurora/list_item_data.dart';
import 'package:internal_aurora/list_item_info.dart';
import 'package:internal_aurora/list_separated.dart';
import 'package:internal_aurora/theme/colors.dart';
import 'package:internal_aurora/theme/theme.dart';
import 'package:platform_channels_qt_demo/platform_channels_qt_demo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final PlatformChannelsQtDemo _plugin = PlatformChannelsQtDemo();
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: internalTheme,
home: Scaffold(
appBar: AppBar(
title: const Text('Platform Channels Qt Demo'),
),
body: ListSeparated(
children: [
const ListItemInfo("""
An example of a platform-dependent plugin that works with the
Aurora OS via the Platform Channels and Qt signal/slot.
"""),
ListItemData(
'State network connect',
InternalColors.purple,
description:
'Listen status network connect through Platform Channels with use Qt',
stream: _plugin.stateNetworkConnect(),
),
],
),
),
);
}
}
Теперь можно запустить приложение. В корне проекта выполнить команду Flutter CLI для сборки и запуска приложения:
flutter-aurora run
Примечание:
Для сборки доступны 3 архитектуры, более детально с этим вопросом можно ознакомиться в разделе "Целевая архитектура".
После успешной сборки можно наблюдать следующий вывод в терминале:
┌─ Result ────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ./build/aurora/psdk_5.0.0.60/aurora-x64/release/RPMS/ru.aurora.platform_channels_qt_demo_example-0.1.0-1.x86_64.rpm │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘