Start Debugging

Миграция приложения Flutter 2 на Flutter 3.x: чек-лист по null safety

Руководство с зафиксированными версиями для перевода устаревшего приложения Flutter 2.x на актуальный выпуск Flutter 3.x, где миграция на sound null safety является жёстким барьером: почему нужен путь в два шага через Dart 2.19, что делает dart migrate и что ломается по дороге.

Если у вас всё ещё есть приложение Flutter 2.x, отказавшееся от sound null safety, перепрыгнуть сразу на актуальный выпуск Flutter 3.x не получится. Dart 3, появившийся в Flutter 3.10 (май 2023), полностью убрал небезопасный (unsound) null safety и удалил инструмент dart migrate, поэтому обновление является миграцией в два шага: сначала сделать кодовую базу null-safe на Dart 2.19 (Flutter 3.7.x), затем перейти на последний выпуск Flutter 3.x. Закладывайте день на небольшое приложение и от трёх до пяти дней на крупное с множеством транзитивных зависимостей. Этап null safety и есть барьер; всё остальное (Gradle, минимальные версии iOS, удалённые виджеты) механично, как только анализатор станет зелёным. Это руководство фиксирует Flutter 2.10 / Dart 2.16 как точку отсчёта и актуальную линию Flutter 3.x с Dart 3 как цель.

Почему это путешествие в два шага, а не в один

Инстинкт подсказывает скачать новейший Flutter, выполнить flutter pub get и чинить то, что сломается. С unsound-приложением Flutter 2 это сразу же проваливается, потому что нужного инструмента больше нет в том SDK, на который вы обновляетесь.

Sound null safety появился как опциональный в Dart 2.12 (Flutter 2.0, март 2021). Два года смешанный режим позволял null-safe и устаревшим библиотекам сосуществовать. Dart 3 положил этому конец. Из официального руководства: “In Dart 3, null safety is built in; you cannot turn it off.” Библиотека, которая так и не была мигрирована, во время разрешения зависимостей выдаёт:

Because pkg1 doesn't support null safety, version solving failed.
The lower bound of "sdk: '>=2.9.0 <3.0.0'" must be 2.12.0 or higher to enable null safety.

а во время выполнения, на старом движке, Library doesn't support null safety. Интерактивный инструмент dart migrate, который это исправляет, был удалён в Dart 3. Dart 2.19.6 является последним SDK, который его содержит. Поэтому единственный поддерживаемый путь таков: мигрировать на null safety, пока вы ещё на SDK Dart 2.x, и только потом обновлять SDK.

Зачем мигрировать в 2026 году

Что ломается

Серьёзность показывает, насколько вероятно изменение сломает типичное приложение, а не насколько сложно его исправить.

ОбластьИзменениеСерьёзность
Null safetyРежим unsound удалён в Dart 3; каждая строка вашего кода и каждая зависимость должны быть null-safeвысокая
Инструмент dart migrateУдалён в Dart 3; существует только до Dart 2.19.6высокая
Ограничение SDKНижняя граница в pubspec.yaml должна быть 2.12.0, затем 3.0.0высокая
ЗависимостиЛюбой пакет без null-safe версии блокирует всё разрешениевысокая
Удалённые устаревшие виджетыThemeData.accentColor, textSelectionColor, toggleableActiveColor удалены в чистке устаревших API в 3.0высокая
Инструментарий AndroidМинимальные версии Gradle, Android Gradle Plugin и Kotlin поднялись; старые build.gradle падаютвысокая
Минимум iOSМинимальная цель развёртывания поднята до iOS 12; 32-битный armv7 отброшенсредняя
Встраивание плагиновПриложения всё ещё на встраивании v1 Android должны перейти на v2средняя

Предполётный чек-лист

Сделайте всё это до того, как трогать код:

Шаги миграции

  1. Зафиксируйте Flutter 3.7.12 (Dart 2.19) для миграции. Это последний инструментарий, в котором есть dart migrate. С fvm выполните в проекте fvm use 3.7.12, затем fvm flutter --version для подтверждения Dart 2.19.x. Проверка: fvm flutter pub get разрешается без ошибки SDK.

  2. Проверьте готовность зависимостей до миграции собственного кода. Выполните dart pub outdated --mode=null-safety. Он печатает таблицу, показывающую, у каких пакетов уже есть null-safe версии, а у каких нет. Проверка: каждая прямая зависимость показывает разрешимую null-safe версию в столбце “Upgradable” или “Resolvable”. Если нет, найдите замену или сделайте форк сейчас, прежде чем идти дальше.

  3. Обновите зависимости до их null-safe версий, снизу вверх. Зависимости мигрируют по порядку: если C зависит от B, который зависит от A, то A должен стать null-safe первым. Инструменты сами управляют порядком, когда вы выполняете dart pub upgrade --null-safety, а затем dart pub get. Проверка: pubspec.lock теперь перечисляет null-safe версии, и dart pub get завершается с кодом 0.

  4. Запустите интерактивный инструмент миграции на вашем коде. Из корня проекта выполните dart migrate. Он анализирует весь пакет, предлагает нулабельность для каждого типа и поднимает локальный веб-интерфейс, где вы просматриваете каждый выведенный ?, late и required. Где он ошибается, подскажите ему маркерами-подсказками в исходнике: /*?*/ форсирует nullable, /*!*/ форсирует non-nullable, /*late*/ отмечает позднюю инициализацию, /*required*/ отмечает обязательный параметр. Проверка: инструмент сообщает о нуле оставшихся ошибок анализа в своей сводке, прежде чем вы примените изменения.

  5. Примените миграцию и поднимите ограничение SDK. Нажмите “Apply migration”. Инструмент перезаписывает ваши файлы .dart, удаляет комментарии-подсказки и устанавливает нижнюю границу в pubspec.yaml:

    # pubspec.yaml -- after the null safety migration, still on Dart 2.19
    environment:
      sdk: '>=2.12.0 <3.0.0'

    Проверка: dart analyze не сообщает об ошибках, а flutter test проходит на Flutter 3.7.12. Закоммитьте это как отдельную контрольную точку: теперь у вас есть sound, null-safe приложение, которое всё ещё работает на старом инструментарии.

  6. Переключитесь на актуальный инструментарий Flutter 3.x. Теперь сделайте второй шаг. Выполните fvm install stable и fvm use stable (или вашу зафиксированную цель вроде 3.35.x). Поднимите ограничение SDK до Dart 3:

    # pubspec.yaml -- targeting the current Flutter 3.x / Dart 3 line
    environment:
      sdk: '>=3.0.0 <4.0.0'
      flutter: '>=3.10.0'

    Выполните flutter pub get. Проверка: разрешение проходит успешно. Если оно падает с “version solving failed”, какой-то зависимости всё ещё не хватает ограничения Dart 3; выполните dart pub outdated, чтобы её найти. Эта же ошибка возникает и из-за простых опечаток в pubspec; см. исправление version solving failed для полного дерева решений.

  7. Устраните ошибки удалённых и устаревших API с помощью dart fix. Flutter 3.0 удалил API, которые были помечены устаревшими в линии 2.x. Выполните dart fix --dry-run для предпросмотра, затем dart fix --apply, чтобы автоматически переписать механические. Частые жертвы это свойства темы: ThemeData.accentColor исчез, что в точности и есть ошибка компиляции accentColor, на которой спотыкается почти каждое обновление с 2 на 3. Проверка: flutter analyze чист.

  8. Почините инструментарий сборки Android. Папка android/ от Flutter 2 почти всегда содержит версии Gradle, AGP и Kotlin, слишком старые для нового встраивания. Обновите android/gradle/wrapper/gradle-wrapper.properties, android/build.gradle и android/app/build.gradle до версий, которых ожидает текущий Flutter (шаблон flutter create для одноразового приложения является эталоном). Проверка: flutter build apk --debug проходит успешно. Если вы наткнётесь на AndroidX conflict, исправление конфликта AndroidX проводит через разрешение.

Проверка

Выполните этот дымовой тест после шага 8 на обеих платформах:

План отката

Миграция на null safety на уровне кода фактически односторонняя: как только типы несут ? и late, откат вручную непрактичен. Именно поэтому шаг 5 является отдельным коммитом. Если шаг с Dart 3 (шаги с 6 по 8) пойдёт не так, вы не откатываете работу по null safety: вы делаете git reset --hard обратно к контрольной точке шага 5, которая является полностью рабочим null-safe приложением на Flutter 3.7.12, и повторяете подъём инструментария. Сохраняйте тег pre-flutter3-migration до тех пор, пока новая сборка не проведёт цикл выпуска в продакшене.

Подводные камни, на которые мы наткнулись

Заброшенная зависимость без null-safe версии. Это безоговорочно самый частый блокер. dart pub outdated --mode=null-safety помечает её, но решение за человеком: замените пакет, форкните и мигрируйте сами или вендорьте его. Решите это в предполётной проверке, потому что обнаружить это в середине миграции означает откат назад.

late как костыль. Инструмент миграции охотно пометит поле как late, чтобы не заставлять вас предоставлять инициализатор. Каждый late это отложенный LateInitializationError, ожидающий путь кода, который читает до записи. Проверьте каждый, вставленный инструментом, и предпочтите настоящий nullable-тип или инициализацию в конструкторе там, где жизненный цикл не герметичен.

dynamic скрывает null от анализатора. Null safety защищает только статически типизированный код. Поля и JSON-словари, типизированные как dynamic, всё ещё пропускают null, который падает в месте использования. Миграция это подходящий момент дать вашим фабрикам fromJson настоящие типы; неверно типизированное поле там бросает FormatException или падение по null, которое система типов не может перехватить. Если вы много парсите JSON, затяните эти модели, пока вы здесь.

Встраивание плагинов v1. Приложения старше встраивания v2 Android падают при сборке в текущем Flutter с ошибкой MainActivity или FlutterApplication. Перегенерируйте android/app/src/main/.../MainActivity.kt из текущего шаблона и удалите старые ссылки на GeneratedPluginRegistrant.

Пропуск промежуточного инструментария. Заманчиво установить текущий Flutter и попытаться продавить силой. Без dart migrate вы вручную аннотируете тысячи типов по ошибкам анализатора, что в точности и есть работа, которую автоматизирует инструмент. Сделайте два шага.

Связанное

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад