Документация
ОС Аврора 3.32.7

Реализации плагина с использованием 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 │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Мы используем cookies для персонализации сайта и его более удобного использования. Вы можете запретить cookies в настройках браузера.

Пожалуйста ознакомьтесь с политикой использования cookies.