Идея писать мультиплатформенные приложения уже далеко не нова. Flutter так же предоставляет возможность это делать. В этой статье я постараюсь описать два подхода запуска мобильного приложения на десктопе, которые я сам использую для разработки мобильных приложений. Я перестал запускать эмулятор и симулятор во многом потому, что появилась возможность обойтись без них. Тем, кому интересна идея, добро пожаловать под кат.

Итак, зачем вообще писать десктоп версию минуя эмуляторы и симуляторы?

Во-первых, ресурсы рабочей станции. В моем текущем коммерческом проекте используется очень много анимаций и различных «тяжелых» виджетов. На моем весьма неплохом ноутбуке с i7, 16ГБ оперативки и видеокартой GTX1050Ti процессор на весьма сложных переходах и анимациях подскакивает до 30-35% в debug режиме, оперативки эмулятор андройда «кушает» до 2ГБ. Кадры, естественно, теряются и анимации движутся «рывками», что не дает нормально оценить полноту картины. При запуске десктоп версии, мой процессор «отдыхает» и нагружается при тех же анимациях до 3-4% и это, на минуточку, экономия процессора в 8-10 раз и оперативки 300-400МБ.

Во-вторых, возможность быстро просмотреть правильность расстановки виджетов на различных девайсах. Просто взять окошко и растянуть, либо сжать до минимальных размеров и это громадная экономия своего личного времени и времени коллег-тестировщиков.

Первый вариант запуска десктоп версии


Используя, теперь уже часть Flutter — flutter-desktop-embedding

Здесь все просто. Копируем папки windows, linux, macos к себе в рабочий проект. Прописываем в консоли команду разрешения использовать библиотеки ОС:

flutter config --enable-windows-desktop
flutter config --enable-linux-desktop
flutter config --enable-macos-desktop

Все. Теперь у нас при выборе девайса появляется, в зависимости от платформы, эмулятор десктоп версии с названием Linux, Macos, Windows. Выбираем, запускаем, работаем.
Получаем идентичную эмулятору платформу с Hot Reload и Hot Restart.

Плюсы:

  1. самые необходимые плагины, например, file_chooser (выбор папок в системе), window_size (изменение размеров окна) уже есть
  2. все работает «из коробки»
  3. компиляция release сборки

Минусы:

  1. нет нормального обработчика хоткеев, например, CTRL+C, CTRL+V
  2. расширения и плагины пишутся раздельно для каждой платформы, в своих подпапках windows, linux, macos

Второй вариант запуска десктоп версии


Используя неофициальный плагин go-flutter-desktop, который предлагает для разработки всю мощь языка Go
Для его установки потребуется hover и библиотека рендеринга glfw. После того как установили все нужные нам зависимости, находим любой пример работы, например, keyboard_event, копируем целиком папку Go и в корне нашей программы пишем:

hover run. Команда все сделает за нас — установит нужные зависимости и запустит десктоп версию.
Получаем так же потребляющую мало ресурсов версию приложения для win, mac, linux и так же с хот релоадом и хот рестартом, вдобавок уже с работающим обработчиком CTRL+C, CTRL+V.

Основные плюсы:

  1. возможность писать плагины на Go для коммуникации Flutter и десктоп части
  2. одна кодовая база под все платформы
  3. огромное количество модулей на Go, а если модулей нет, всегда можно написать самому и это будет работать — запуск командной строки, нативная нотификация, выгрузка логов в файл, запуск нативного приложения и т.д.
  4. библиотека glfw уже предлагает множество функций для работы с программой, например, изменение, ограничение размеров окна
  5. основные нужные плагины уже реализованы, file_chooser (выбор файла), path_provider (получение темповой директории, директории для загрузок), video_player, который использует ffmpeg для проигрывания видео

Минусы:

  1. не все плагины нужные есть. Например, нет (на момент написания статьи) плагина для сворачивания приложения в трей. Так же у меня не получилось запустить плагин video_player. В консоли вываливается ошибка «нет обработчика нужного канала». Похоже, флаттеровский плагин поменял API и go-версия не успела обновиться
  2. для совместного использования плагина и, например, VSCode требуется совершить еще дополнительные телодвижения, т.к. hover запускает приложение из консоли и нужно аттачиться к процессу

В помощь пример launch.json:
{
  "name": "go-flutter_desktop",
  "request": "attach",
  "deviceId": "flutter-tester",
  "observatoryUri": "http://127.0.0.1:50300/",// "${command:dart.promptForVmService}",
  "type": "dart",
  "program": "lib/desktop_main.dart" // Dart-Code v3.3.0 required
}


и потребуется установить плагин для удобства в VSCode — Hover

Пример плагина для эмуляции размеров окна эмулятора


Проще всего скопировать папку Go из репозитория примеров проекта, например, из github.com/go-flutter-desktop/examples/tree/master/text_demo

Далее в директории Go создаем папки plugins/desktop_utils и в ней файл с нашим будущим плагином, назовем его main.go и впишем в него

следующий код:
package desktop_utils

import (
	"github.com/go-flutter-desktop/go-flutter"
	"github.com/go-flutter-desktop/go-flutter/plugin"
	"github.com/go-gl/glfw/v3.3/glfw"
)

const channelName = "go-test/desktop_utils"

type DesktopUtilsFlutterPlugin struct {
	window *glfw.Window
	channel *plugin.MethodChannel
}

var _ flutter.Plugin = &DesktopUtilsFlutterPlugin{}
var _ flutter.PluginGLFW = &DesktopUtilsFlutterPlugin{}

func (p *DesktopUtilsFlutterPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
	channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
	
	channel.HandleFunc("set_window_size", p.setWindowSizeHandler)

	return nil
}

func (p *DesktopUtilsFlutterPlugin) InitPluginGLFW(window *glfw.Window) error {
	p.window = window

	return nil
}

func (p *DesktopUtilsFlutterPlugin) setWindowSizeHandler(arguments interface{}) (reply interface{}, err error) {
	argumentsMap := arguments.(map[interface{}]interface{})

	var minW = int(argumentsMap["minW"].(int32))
	var minH = int(argumentsMap["minH"].(int32))
	var maxW = int(argumentsMap["maxW"].(int32))
	var maxH = int(argumentsMap["maxH"].(int32))

	p.window.SetSizeLimits(minW, minH, maxW, maxH);

	return nil, nil
}

где channelName — имя нашего канала для связки с Flutter
minW, minH, maxW, maxH — минимальные/максимальные размеры окна

Инициализируем наш файл как Go-модуль: go mod init desktop_utils (появится файл go.mod).

Идем в директорию Go, находим файлик go.mod, дописываем локальную директорию нашего плагина: replace desktop_utils => ./plugins/desktop_utils
и добавляем наш плагин в options.go в строчку опций:

var options = []flutter.Option{
	flutter.WindowInitialDimensions(InitW, InitH),

	flutter.AddPlugin(&desktop_utils.DesktopUtilsFlutterPlugin{}),
}

На этом Go-часть закончена.

Flutter часть максимально проста: нам нужно только создать канал (MethodChannel) с нашим названием: go-test/desktop_utils и передать в него нужные нам параметры длины и ширины:

const MethodChannel _utilsChannel = MethodChannel('go-test/desktop_utils');

_utilsChannel.invokeMethod(
  'set_window_size',
   <String, dynamic>{
     'minW': 400,
     'minH': 600,
     'maxW': 800,
     'maxH': 1200,
   },
 );

На этом все. Наш плагин готов.

gif работы плагина


Пример можно посмотреть у меня в репозитории.

Третий вариант запуска десктоп версии


Можно использовать плагин flutter-rs и язык Rust.

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

Вместо заключения


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