Библиотеки во Flutter-проекте играют важную роль в разработке приложений. Они представляют собой совокупность предопределённых функций, классов и компонентов, которые могут быть использованы разработчиками для упрощения и ускорения процесса создания приложений.
Типы библиотек
Библиотеки бывают двух типов:
- пакеты (англ. packages) — содержат только код, написанный на Dart и Flutter;
- плагины (англ. plugins) — помимо Dart и Flutter имеют платформо-специфичный код, также называемый нативным. Хоть Flutter и позволяет писать сразу под несколько платформ, в некоторых случаях может потребоваться использование специфической функциональности, которая доступна только на одной из платформ. Например, это может быть интеграция с уведомлениями операционной системы, использование специфических интерфейсов пользователя, доступ к аппаратным функциям устройства (камере, датчикам, Bluetooth и проч.) или взаимодействие с другими приложениями, доступными только на конкретной платформе.
pub.dev
Когда мы говорим о библиотеках во Flutter, сразу вспоминается популярный ресурс — pub.dev. Данный сайт — официальный репозиторий пакетов для Dart и Flutter, он предоставляет разработчикам доступ к широкому спектру пакетов, которые могут использоваться для Flutter-проектов. На нём размещены пакеты, созданные сообществом Dart и Flutter, а также пакеты, разработанные и поддерживаемые самой командой Google.
Страница каждого пакета содержит его название, актуальную версию, дату публикации, автора и другую информацию, которая может быть полезна при использовании пакета. Ниже представлен скриншот страницы библиотеки firebase_messaging версии 14.6.1. Можно увидеть, что она была выбрана как Flutter Favorite — это одна из «наград» на сайте, которая говорит о высочайшем качестве библиотеки.
Для работы с pub.dev мы можем использовать инструмент pub
, с которым мы успели познакомиться в первом параграфе.
Зависимости
Для добавления зависимости в проект используется поле dependencies
в файле pubspec.yaml
, под ним указывается название библиотеки, её источник и версия, если она обязательна для выбранного типа источника. Зависимости бывают четырёх видов с точки зрения источников.
Зависимость из pub.dev
Зависимость, которая была опубликована на сайте pub.dev, официальном публичном репозитории библиотек для Dart и Flutter.
dependencies:
dependency_name: <version>
Git-зависимость
Зависимость из Git-репозитория.
dependencies:
<dependency_name>:
git: https://github.com/<repo_url>
## Зависимость лежит по указанному пути в корне проекта
Помимо указания ссылки мы можем добавлять параметры ref
и path
:
ref
— позволяет указывать номер коммита, название ветки или тега, чтобы тянуть зависимость не из веткиmaster
илиmain
, а исходя из идентификатора;path
— позволяет указывать пути до библиотеки, если пакет лежит не в корне проекта.
dependencies:
<dependency_name>:
git:
url: https://github.com/<repo_url>
ref: some-branch
path: some-path
Локальная зависимость
Зависимости, которые лежат в локальной файловой системе, могут устанавливаться локально. Для этого нужно прописать полный или относительный путь до этой зависимости.
dependencies:
<dependency_name>:
path: <your_local_path>
Hosted-зависимость
Зависимость, которая находится на pub.dev, публична и доступна для всех разработчиков. Не всегда разработчики готовы делиться своими наработками, так что существует возможность загружать пакет с удалённого хранилища пакетов, который имеет такой же API, что и pub.dev. Для установки таких зависимостей требуется указывать URL в параметре hosted.
dependencies:
<dependency_name>:
hosted: https://some-package-server.com
version: <version>
Такое приватное хранилище пакетов можно создать с помощью специального пакета unpub
, о котором можно прочитать по ссылке.
Версионирование зависимостей
Как мы узнали в предыдущем параграфе, библиотеки имеют обязательное поле 'version' в файле pubspec.yaml
.
Для определения версии пакета в Dart используется семантическое версионирование (англ. semantic versioning), которое состоит из трёх чисел, разделённых точками: мажор.минор.патч
(англ. major.minor.patch
). Каждое из чисел обозначает различные изменения в пакете:
- мажор (основная версия) — изменения в API, которые обратно несовместимы (англ. breaking changes). Такие изменения могут повлиять на существующий код, который использует предыдущую версию библиотеки, и могут потребовать изменений или модификаций в этом коде, чтобы он продолжал работать с новой версией;
- минор (англ. minor) — добавление новой функциональности в существующее API, которое не нарушает обратную совместимость;
- патч (англ. patch) — исправление ошибок в пакете, которые не влияют на API.
В Dart можно использовать операторы версионирования для указания ограничений версий зависимостей. Операторы версионирования, которые также называют констрейнтами (англ. constraints), включают в себя следующие:
- any — используется для указания того, что проект совместим со всеми версиями зависимости. Это означает, что проект может использовать любую версию зависимости, которая доступна на момент установки;
- x.y.z — установится версия x.y.z, где x — мажор, y — минор, а z — патч;
- >x.y.z — установится максимально возможная версия из тех, что выше x.y.z;
- >=x.y.z — установится максимально возможная версия из тех, что выше, или равная версии x.y.z;
- <x.y.z — установится максимально возможная версия из тех, что ниже x.y.z;
- <=x.y.z — установится максимально возможная версия из тех, что ниже, или равная версии x.y.z;
- ^x.y.z — расшифровывается как >=x.y.z <(x+1).0.0 ^1.2.3 эквивалентно >=1.2.3 <2.0.0. Однако для версий с мажором, равным нулю, работает по-другому: ^0.1.2 эквивалентно >=0.1.2 <0.2.0. Если мажор равен нулю, то библиотека ещё в бета-версии и любые минорные изменения могут быть обратно несовместимы.
О семантическом версионировании можно прочесть в официальной документации. В ней же, в последнем абзаце, рассказано о библиотеках с нулевой мажорной версией.
Также можно указывать диапазон версий с помощью комбинирования <, <=, >, >=. Например, конструкция >=1.12.8 <=3.0.0 означает, что мы хотим установить версию от 1.12.8 включительно до 3.0.0 включительно.
Но что значит максимально возможная версия? Чтобы это понять, нужно узнать ещё об одном варианте разделения зависимостей: прямые, транзитивные и совместно используемые.
Вот так выглядит прямая зависимость, мы добавляем пакет A напрямую в наше приложение.
Транзитивная (англ. transitive) же зависимость подтягивается из пакета, который мы устанавливаем как зависимость. Это зависимость нашей зависимости.
А общая (англ. shared) зависимость — зависимость, которую подтягиваем и мы, и используемый нами пакет.
Максимально возможная версия — та, которая удовлетворяет диапазону, образующемуся при подтягивании прямых, общих и транзитивных зависимостей. Для иллюстрации выше можно заметить, что мы пытаемся установить пакет в версии ^1.0.0 и одновременно версии ^2.0.0 — так как пересечения нет, возможной для установки версии тоже не существует. Такая ситуация называется конфликтом, и при вызове команды flutter pub get
в консоли появится следующее сообщение:
Because Package A depends on Package B ^2.0.0 and no version of Package A match, Package A requires Package B ^2.0.0.
So, because app depends on both Package B ^1.0.0 and Package A, version solving failed.
Существует три способа решения данной проблемы:
- Поменять версию пакета B на ^2.0.0 в нашем приложении.
- Установить версию пакета A, которая использует версию пакета B ^1.0.0.
- Переопределить зависимости с помощью dependency_overrides. Как следует из названия, мы перезаписываем версию библиотеки. Можно указать пакет, версию и источник в свойстве
dependency_overrides
, иpub
будет использовать эти значения вместо тех, которые указаны вdependencies
или подтягиваются транзитивно.
В нашем случае это работает так, что мы используем пакет A
, который устанавливает транзитивно пакет B
версии 2.0.0
, но мы хотим использовать версию 1.0.0
, и для этого можно указать в файле pubspec.yaml
следующее:
dependency_overrides:
package_B: ^1.0.0
С переопределением версий нужно быть аккуратными. Есть риск, что после переопределения проект перестанет компилироваться, так как библиотеке, которая использует версию выше или ниже, будет не хватать функционала в переопределённой версии. Также не стоит устанавливать в проект библиотеки неактуальных версий, ведь фреймворк и библиотеки развиваются быстро, так что могут появиться несовместимые изменения. Чем дольше не обновлять библиотеки, тем сложнее это сделать потом. Также в новых версиях могут появиться новые возможности и правки недочётов.
Помимо зависимостей, указывающихся в pubspec.yaml
как dependencies
, существуют dev_dependencies. Это зависимости, которые нужны только на этапе разработки, они не попадают в сборку приложения. К ним относится linter, различные зависимости для кодогенерации и аннотаций, зависимости для тестов и другие зависимости, которые не используются для сборки приложения.
dev_dependencies:
flutter_lints: ^2.0.2
pubspec.lock
pubspec.lock
— файл, который автоматически генерируется при успешном запуске команды pub get
в проекте Flutter или Dart. Он содержит информацию о фактических версиях зависимостей, которые были установлены для проекта, включая транзитивные зависимости.
Файл pubspec.lock
содержит информацию о конкретных версиях каждого пакета, установленного в проекте. Это гарантирует, что при повторной установке пакетов будут использованы те же версии зависимостей, что и в предыдущий раз.
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages: # Тут содержится список всех зависимостей
dependency_name:
dependency: transitive # Тут содержится тип зависимости: transitive, shared, direct main, direct overridden
description:
... # Тут содержится короткое описание зависимости, для зависимости с pub.dev тут будут содержаться поля name и url с названием и путём до зависимости соответственно
source: ... # Тут содержится источник зависимости, для зависимости с pub.dev тут будет стоять hosted
version: ... # Тут — версия зависимости, которая установилась после прогона команды flutter pub get
sdks: # В этой части есть констрейнты для dart и flutter
dart: ">=2.15.0 <3.0.0" # Устанавливаем Dart версии выше или равной 2.15.0, но меньше 3.0.0
flutter: ">=2.8.0" # Устанавливаем Flutter версии выше или равной 2.8.0
Стоит ли хранить этот файл в Git-репозитории
Для приложений — да. Он гарантирует, что при локальном запуске на разных платформах у разработчиков не разойдутся версии библиотек. Это можно объяснить на таком примере: в команду может прийти новый разработчик, запустить команду pub get
— и у него не установятся новые мажорные версии библиотек, ведь lock-файл уже существует и не поменяется.
Однако для библиотек этот файл в репозитории следует игнорировать. Библиотеки должны содержать актуальные версии своих зависимостей, чтобы разработчик библиотеки не использовал старый сгенерированный lock-файл, с которым у него всё работает, а пользователи не устанавливали новые версии транзитивных библиотек, с которыми проект несовместим.
Создание библиотеки
Для создания Flutter-проекта используется команда flutter create
. Для создания библиотеки или плагина указывается флаг --template
. Команда flutter create --template=package package_name
используется для создания пакета, а flutter create --template=plugin plugin_name
— для создания плагина.
Для библиотек принято создавать вот такую структуру:
Отличие от структуры проекта в том, что библиотека содержит папку lib/src
. Все реализации будут храниться в lib/src
и будут приватны, то есть недоступны для внешних пользователей. При этом в файлах, лежащих непосредственно в папке lib, будут файлы с экспортируемым функционалом с указанием путей до функционала, который хочется предоставить пользователю библиотеки.
export
используется в файле, который находится в корне библиотеки и который будет необходим для импорта в других частях проекта, зависимого от библиотеки. Часто названия экспорт-файлов совпадают с названием библиотеки.
// my_library.dart
library my_library; // Уникальный тег, должен совпадать с названием файла
export 'src/file1.dart'; // Экспортируем весь функционал файла file1.dart
export 'src/file2.dart' show ClassA; // Экспортируем только класс ClassA из файла file2.dart
export 'src/file3.dart' hide ClassB; // Экспортируем всё, кроме класса ClassB, из файла file3.dart
Для использования функционала из файлов проекта или из библиотеки следует использовать импорты.
import 'package:my_package/my_package.dart'; // Импортируем весь функционал, экспортированный в файле my_package.dart библиотеки my_package
import 'src/some_file.dart' show SomeClassThatOurClientWillUse; // Импортируем только класс SomeClassThatOurClientWillUse
import 'src/some_file.dart' hide SomeClassThatOurClientDoesNotNeed; // Импортируем всё, кроме класса SomeClassThatOurClientDoesNotNeed
import 'src/some_file.dart' as someName; // Импортируем файл как someName. Потом к функционалу можно будет обращаться с префиксом someName: someName.<ClassName>.<MethodToCall>()
Помимо экспорт-файлов у библиотек есть ещё одна особенность — файл CHANGELOG.md
.
CHANGELOG.md
Файл CHANGELOG.md
крайне рекомендуется актуализировать. В нём принято оставлять информацию о том, что было добавлено, исправлено, удалено в новой версии библиотеки. А ещё в нём иногда рассказывают о будущих релизах и о том, что не успели сделать в последней версии. Вот так выглядит часть файла для разработчика:
# 2.0.2
Fixed an assert error if a `family` depends on itself while specifying `dependencies`.
А вот так — для того, кто читает информацию о последней версии:
Как выбрать хорошую библиотеку?
К сожалению, единой инструкции нет, но можно обратить внимание на несколько параметров:
- Насколько давно библиотека была обновлена и поддерживается ли она сейчас
На pub.dev это можно увидеть сразу под названием библиотеки. Например, на момент создания статьи библиотека firebase_messaging
была опубликована последний раз пять дней назад.
- Насколько подробен
CHANGELOG
При поднятии версии библиотеки это поможет разработчику понять, какой функционал стоит перепроверить или поддержать.
- Сколько обращений в
issue_tracker
И насколько быстро разработчик правит все замечания и отвечает на новые открытые вопросы.
- Различные метрики популярности
Можно смотреть на звёздочки на GitHub или на секцию Scores на pub.dev
— в этой секции показаны многие метрики, включая популярность библиотеки, лайки и собственную метрику pub points, которая показывает уровень библиотеки, актуальность зависимостей, добавлена ли документация, следует ли библиотека гайдлайнам и совместима ли она с Dart
версии 3.
Что такое совместимость с Dart 3?
Null safety — это концепция, которая обеспечивает безопасную работу с опциональными типами значений. Она стала доступной в Dart начиная с версии 2.12, требуя от проектов и библиотек поддержки безопасности от null-значений. До Dart
версии 3 проект можно было запускать с флагом --no-sound-null-safety
, игнорируя предупреждения о небезопасной работе с null-
значениями. С третьей мажорной версии так не сделаешь — все библиотеки и сам проект обязаны безопасно работать с nullable-значениями. Новый пункт Dart 3 compatibility в Scores показывает баллы, которые дают за полную поддержку null safety.
Источник: https://pub.dev/packages/dio/score
Полезные советы
-
Не забывайте запускать команду
flutter pub get
перед запуском проекта, чтобы установить все необходимые зависимости. Если проект не собирается по непонятным причинам, может помочь командаflutter clean
— она очистит.dart_tools
и удалит возможно повреждённый кеш. После неё снова предстоит переустановить зависимости с помощью командыflutter pub get
. -
Можно использовать команду
flutter pub get --offline
для установки библиотек из кеша, если в данный момент нет соединения с интернетом. -
Когда мы запускаем команду
flutter pub get
, создаётся папка.pub_cache
, она содержит закешированные библиотеки, которые мы установили. Бывает такое, что кеш слетает. К примеру, когда в него добавилась кодогенерация или мы сами непреднамеренно изменили код одной из библиотек. Ни в коем случае не стоит модифицировать файлы из кеша. Вместо этого стоит запуститьflutter pub cache clean
для полной очистки кеша иflutter pub cache repair
для переустановки каждой библиотеки. -
В некоторых случаях возникают ошибки, связанные с платформо-специфичными библиотеками. Это может произойти, если указанные библиотеки не были установлены. Для очистки нативных зависимостей с целью их переустановки следует запустить
pod deintegrate && pod install
для iOS иgradlew clean
для Android.