Пишем и публикуем идеальный пакет для Flutter
Думаю, многие разработчики хотя бы раз в жизни хотели поделиться своими наработками с сообществом. Уж точно все пользовались тем, чем делятся другие. Мое мнение на этот счет примерно такое: если ты делаешь что-то для себя и можешь это сделать таким, чтобы этим могли пользоваться другие с, относительно небольшим количеством трудозатрат -- делай это.
С чего начинается путь велосипедных дел мастера во Flutter?
Краткий ответ -- с pub.dev. Более длинный -- с ознакомления с документацией. Кстати -- вот она: https://flutter.dev/docs/development/packages-and-plugins/developing-packages. Начнем с самого начала -- во Flutter/Dart пакеты разделяются на два типа:
- Собственно, пакет (Dart package).
- Плагин (Plugin package).
Отличия у них такие: простые пакеты содержат только Dart-код и могут содержать зависимости от Flutter. Плагины -- это пакеты, имеющие связь с нативным кодом. Это может быть Java / Kotlin, Objective-C / Swift и с недавних пор, пожалуй, сюда можно отнести и C++ / С / etc, так как Flutter официально ступил на земли десктопов. Есть еще такие вариации, когда ты не используешь платформенный код как таковой, но при этом используешь те же плюсы через FFI. Это можно отнести, скорее всего, к плагинам. Но на самом деле в разрезе данной статьи это не играет большой роли. Далее оба этих типа будут называться одним словом -- пакет, без разделения на подтипы.
Небольшое интро провели -- идем дальше. Как советует та же дока, чтобы начать писать пакет необходимо выполнить следующую команду:
flutter create --template=package my_package_name # or flutter create --template=plugin my_plugin_name
Я же воспользуюсь возможностями IDE, поэтому тем, кто сидит на Android Studio / IDEA, можно сделать следующее:
1.Создаем новый проект.
2.Выбираем Flutter в качестве основы (предварительно -- вы должны установить Flutter + Dart плагины.
3.Выбираем тип проекта -- Plugin / Package (остальные свойства выбираем исходя из своих задач).
Отлично. Проект создан, что дальше?
Я написал пакет -- как его опубликовать?
Не торопись, ковбой! Прежде чем публиковать пакет, стоит помнить о следующих вещах:
- твой опубликованный пакет будет таковым навсегда (пока существует pub.dev)
- твой пакет должен соответствовать хотя-бы каким-то минимальным требованиям к качеству кода;
- для каждой новой версии пакета необходимо указывать изменения в файле CHANGELOG.md;
- прежде чем публиковаться, необходимо заботливо положить в корень твоего проекта файлик LICENCE с лицензией, согласно которой он будет доступен;
- pubspec.yaml в твоем проекте должен содержать обязательные поля, содержащие информацию о твоем проекте;
- все зависимости твоего пакета должны быть опубликованы на pub.dev.
Давай пройдемся по всем этим пунктам не по порядку.
Качество кода
Все опубликованные пакеты автоматически оцениваются по нескольким критериям качества кода. Рассмотрим их подробнее.
Необходимо документировать все публичные поля и методы, которые есть в твоем проекте.
Тут все просто -- используем /// для всего, что будет доступно пользователям твоей прекрасной библиотеки. Например, вот так:
/// Describes a one cell of animated text: /// We change "100" to "250" /// Then, we have 3 animated tokens in not reversed flow: /// 1th 2th 3th /// | 2 | 5 | _ | /// | 1 | 0 | 0 | /// | _ | _ | _ | class AnimatedToken { AnimatedToken({ @required this.top, @required this.center, @required this.bottom, @required this.direction, @required this.topSize, @required this.centerSize, @required this.bottomSize, this.axisY, this.axisYOld, this.axisX, this.axisXTween, this.opacity, this.opacityOld, }); /// | top | /// | center | /// | bottom | final String top; /// | top | /// | center | /// | bottom | final String center; /// | top | /// | center | /// | bottom | final String bottom; /// Describes in which direction this token will move final Direction direction; /// Size of top letter final Size topSize; /// Size of center letter final Size centerSize; /// Size of bottom letter final Size bottomSize; /// Animation in Y axis for new letter Animation<double> axisY; /// Animation in Y axis for old letter Animation<double> axisYOld; /// Animation in X axis for the same letter (old == new) Animation<double> axisX; Tween<double> axisXTween; /// If token is Direction.bottom - opacity ween will be from /// If Direction.top - 0 -> 1 Animation<double> opacity; /// If token is Direction.bottom - opacity ween will be from /// If Direction.top - 0 -> 1 Animation<double> opacityOld; @override String toString() => '''AnimatedToken { top: $top -> $topSize center: $center -> $centerSize bottom: $bottom -> $bottomSize direction: $direction }'''; }
Не буду говорить, что такая практика позволяет и самому, спустя какое-то время, понимать, что тут к чему, но она помогает и юзерам твоего пакета. Например, в той же IDEA / AS есть возможность отображения комментариев к коду по наведению курсора (прямо как в VSCode).
Желательно использовать dart fmt -- форматтер кода, настроенный в соответствии с рекомендуемыми параметрами
Тут все довольно просто. Используем зависимость pedantic или effective_dart (лично я предпочитаю pedantic, т. к. он более строгий из коробки). Затем создаем файл analysis_options.yaml и используем в нем нашу зависимость:
include: package:pedantic/analysis_options.yaml
Если есть личные предпочтения в том, как должен выглядеть код, то можно дополнять / переопределять правила линтера. В этом помогут этот и этот ресурсы. К слову, кастомизировать можно не только правила линтера, но и общие правила языка (с некоторыми оговорками). Делается это через манипуляции в блоке:
include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-dynamic: false implicit-casts: false errors: todo: ignore mixin_inherits_from_not_object: ignore sdk_version_async_exported_from_core: ignore missing_required_param: error division_optimization: error must_call_super: error always_put_required_named_parameters_first: error avoid_positional_boolean_parameters: error unnecessary_await_in_return: error invalid_use_of_protected_member: error # ... linter: rules: # ...
Вот тут есть весь перечень возможных ошибок / ситуаций, которыми можно управлять. Можно настроить все так, словно ты настоящий маньяк -- мне нравится возможность сделать некоторые warning'и ошибками, и не позволять запускать проект в принципе, к примеру, при наличии в коде обращений к @protected полям и методам.
После всего этого твой код, скорее всего, засияет -- ты увидишь все проблемы, которые стоит исправить заблаговременно. Плюс -- можно настроить IDE на полное автоформатирование кода и перестать беспокоиться, что где-то случайно поставил два пробела вместо одного или фигурная скобка висит не на той строке. Для этого нужно всего-то сделать это:
Работать после таких манипуляций намного приятнее.
Полный список параметров, из которых формируется оценка твоего пакета, показан ниже:
1.Сопроводительные файлы:
2.Документирование кода. Важный момент в этом пункте связан с проектом-примером, который следует располагать в папке example твоего пакета и отражать в этом примере то, как именно следует пользоваться твоим пакетом:
3.Поддержка всех платформ. С этим тоже могут быть проблемы, например, некоторые части стандартной библиотеки не могут быть использованы в Web -- поэтому получить все возможные баллы для некоторых пакетов просто невозможно. Также, из интересного -- после релиза Flutter 2 появилась поддержка десктопных платформ, и для таких пакетов теперь есть пометка об их поддержке. А также такие пакеты стоит писать сразу с null-safety (при Dart >= 2.12).
4.Прохождение форматтера.
5.Ну и последнее -- свежие зависимости -- как мотиватор хотя бы поддерживать твой пакет.
Changelog
Каждое обновление пакета (и публикация первой версии) должно сопровождаться описанием того, что было изменено. Для этого в проекте должен быть специальный файл, в котором описываются изменения. Это довольно просто и выглядит следующим образом:
## [1.1.0] - Add opacity sub-animation for tokens and curves manipulation ## [1.0.1] - Add demo gif and update readme ## [1.0.0] - First release
Для каждой новой версии добавляем строку сверху согласно приведенному шаблону и все будет хорошо.
License
Все тоже весьма просто. Идем, к примеру сюда. Ищем подходящую лицензию, берем ее текст, указываем себя как автора и готово.
Pubspec
Можно ознакомиться с документацией к данному конфигу или просто посмотреть на пример (в нем отражены необходимые для публикации поля):
name: anitex description: Anitex is a implicitly animated text widget, which animates on passed text changes version: 1.2.0 repository: https://github.com/alphamikle/anitex homepage: https://github.com/alphamikle/anitex environment: sdk: ">=2.7.0 <3.0.0" flutter: ">=1.17.0 <2.0.0" dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter pedantic: ^1.9.2 flutter:
Процесс публикации
Сделать это можно таким образом:
pub publish
Результатом выполнения команды будет вывод краткого отчета, например такого:
... ... Package validation found the following potential issue: * ./CHANGELOG.md doesn't mention current version (2.0.0). Consider updating it with notes on this version prior to publication. Publishing is forever; packages cannot be unpublished. Policy details are available at https://pub.dev/policy Package has 1 warning.. Do you want to publish anitex 2.0.0 (y/N)?
Тут показан отчет, когда не все ок -- есть проблемы с пакетом, а значит, публиковать его в таком виде однозначно не стоит, поэтому, чтобы рука не сорвалась, нажав случайно y, стоит пользоваться командой:
pub publish --dry-run
Она покажет те же самые проблемы либо их отсутствие, как тут:
... ... Package has 0 warnings.
И когда ты увидишь заветные 0 warnings -- значит, можно публиковать пакет. Какие еще есть нюансы? Нужно зарегистрироваться на том же pub.dev (с помощью аккаунта Google). А при выполнении команды публикации тебе будет предложено авторизоваться уже в консоли.
Это навечно
Даже если никто и никогда не воспользуется твоим пакетом (надеюсь, что все будет не так), гугл не позволит выполнить операцию, непосредственно, удаления твоего пакета из pub.dev (может только через поддержку, но я не пробовал). Однако, если ты понял, что совершил ошибку -- то ты можешь пометить свой пакет как "Неподдерживаемый". У него появится яркая плашка, которая будет говорить всем твоим потенциальным фанатам, что этот продукт деятельности твоего ума больше не будет развиваться.
Можно зайти еще дальше и сделать пакет Unlisted -- он выпадет из обычного поиска, но по прежнему будет доступен при расширенном поиске -- это нечто среднее между приватным и публичным пакетом.
Также в админке управления твоими пакетами имеется возможность создать так называемого publisher -- некое абстрактное лицо, от имени которого будут опубликованы пакеты. Это удобно для различных комьюнити / компаний, но каких-то особых профитов не дает (дает лычку). Еще для этого необходимо прикупить домен в .dev зоне (можно не только в ней), к которому publisher и будет привязан.
Что еще?
Пакет ты опубликовал -- собрал 110 или 130 баллов, но его никто не использует... Тут начинается самое интересное -- продвижение. Можно писать статьи, приводя расширенные примеры использования твоего пакета и рассказывая в деталях, почему он лучше другого очень похожего решения. На ресурсе на букву M можно встретить множество статей подобного плана. Можно начать, хотя бы, с коллег или, если есть уверенность в себе и своем решении -- использовать его в рабочем проекте. После достижения хотя бы какой-то известности можно попытать удачу и податься, например, сюда. Это коллекция интересных open source решений для Flutter, и там может оказаться и твой прекрасный пакет!
Выводы
Их не особо много -- процесс публикации библиотек в экосистеме Flutter выглядит довольно простым, а сама идея делиться своими наработками с сообществом очень благородна, и, как по мне -- обязательна просто потому, что каждый разработчик пользовался результатом умственного труда других разработчиков и будет весьма справедливым -- внести и свою лепту. К тому же, это полезно и тебе, дорогой друг -- новые знакомства из open source-комьюнити, новые возможности в поиске работы (многие HR'ы ищут разрабов уже и на GitHub), да и просто развитие себя, как технического специалиста.