Миграция приложения 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 году
- Каждый актуальный пакет на pub.dev требует ограничения SDK Dart 3 (
sdk: '>=3.0.0 <4.0.0'). Оставаясь на Flutter 2, вы отрезаны от новых версийhttp,provider,go_routerи всех плагинов Firebase, включая их патчи безопасности. - Dart 3 открыл records, паттерны, запечатанные классы и модификаторы классов. Ничто из этого не компилируется на инструментарии Flutter 2.
- Sound null safety даёт реальный выигрыш в корректности: компилятор доказывает, что
Stringникогда не равенnull, поэтому целый класс паденийNoSuchMethodError: The method '...' was called on nullстановится невозможным выпустить. Если вы когда-либо гонялись за этим багом в продакшене, это и есть исправление на уровне системы типов. - Требования магазинов Apple и Google (минимальные уровни SDK, 64 бита, актуальный инструментарий сборки) ушли далеко за пределы того, что способна обеспечить сборка Flutter 2.
Что ломается
Серьёзность показывает, насколько вероятно изменение сломает типичное приложение, а не насколько сложно его исправить.
| Область | Изменение | Серьёзность |
|---|---|---|
| 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 | средняя |
Предполётный чек-лист
Сделайте всё это до того, как трогать код:
- Закоммитьте чистую базу и поставьте тег:
git tag pre-flutter3-migration. Вы вернётесь к ней не раз. - Запишите ваши точные текущие версии:
flutter --versionиdart --version. Зафиксируйте их где-нибудь, чтобы миграция была воспроизводимой. - Убедитесь, что CI сначала собирает приложение зелёным на старом SDK. Миграция поверх уже сломанной сборки тратит часы впустую.
- Установите инструментарий Dart 2.19 / Flutter 3.7.12 рядом с текущим. Используйте
fvm(Flutter Version Management), чтобы переключаться по проектам:fvm install 3.7.12. - Проведите инвентаризацию зависимостей:
flutter pub deps --no-devи отметьте всё необслуживаемое. Один заброшенный пакет без null-safe версии может остановить всю миграцию.
Шаги миграции
-
Зафиксируйте 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. -
Проверьте готовность зависимостей до миграции собственного кода. Выполните
dart pub outdated --mode=null-safety. Он печатает таблицу, показывающую, у каких пакетов уже есть null-safe версии, а у каких нет. Проверка: каждая прямая зависимость показывает разрешимую null-safe версию в столбце “Upgradable” или “Resolvable”. Если нет, найдите замену или сделайте форк сейчас, прежде чем идти дальше. -
Обновите зависимости до их null-safe версий, снизу вверх. Зависимости мигрируют по порядку: если C зависит от B, который зависит от A, то A должен стать null-safe первым. Инструменты сами управляют порядком, когда вы выполняете
dart pub upgrade --null-safety, а затемdart pub get. Проверка:pubspec.lockтеперь перечисляет null-safe версии, иdart pub getзавершается с кодом 0. -
Запустите интерактивный инструмент миграции на вашем коде. Из корня проекта выполните
dart migrate. Он анализирует весь пакет, предлагает нулабельность для каждого типа и поднимает локальный веб-интерфейс, где вы просматриваете каждый выведенный?,lateиrequired. Где он ошибается, подскажите ему маркерами-подсказками в исходнике:/*?*/форсирует nullable,/*!*/форсирует non-nullable,/*late*/отмечает позднюю инициализацию,/*required*/отмечает обязательный параметр. Проверка: инструмент сообщает о нуле оставшихся ошибок анализа в своей сводке, прежде чем вы примените изменения. -
Примените миграцию и поднимите ограничение 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 приложение, которое всё ещё работает на старом инструментарии. -
Переключитесь на актуальный инструментарий 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 для полного дерева решений. -
Устраните ошибки удалённых и устаревших API с помощью
dart fix. Flutter 3.0 удалил API, которые были помечены устаревшими в линии 2.x. Выполнитеdart fix --dry-runдля предпросмотра, затемdart fix --apply, чтобы автоматически переписать механические. Частые жертвы это свойства темы:ThemeData.accentColorисчез, что в точности и есть ошибка компиляции accentColor, на которой спотыкается почти каждое обновление с 2 на 3. Проверка:flutter analyzeчист. -
Почините инструментарий сборки 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 на обеих платформах:
flutter analyzeсообщает о нуле проблем.flutter testпроходит с тем же числом, что и ваша база до миграции. Снижение означает, что тестовый файл молча не скомпилировался.flutter build apk --releaseиflutter build ios --release --no-codesignоба проходят успешно.- Запустите на реальном устройстве, а не только в симуляторе, и пройдите экраны, которые раньше падали с ошибками null. Sound null safety должен был полностью убрать эти режимы отказа.
- Сравните время запуска приложения и время кадров с базой по тегу. AOT-компилятор Dart 3 обычно быстрее, но проверьте это, а не предполагайте.
План отката
Миграция на 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 вы вручную аннотируете тысячи типов по ошибкам анализатора, что в точности и есть работа, которую автоматизирует инструмент. Сделайте два шага.
Связанное
- Fix: version solving failed в pubspec.yaml
- Flutter: the getter accentColor isn’t defined for the class ThemeData
- Fix: конфликт AndroidX во время сборки Flutter под Android
- Как мигрировать приложение Flutter с GetX на Riverpod
- Provider против Riverpod против Bloc для управления состоянием во Flutter в 2026 году
Источники
- Dart 3 migration guide — null safety обязателен в Dart 3,
dart migrateудалён, сначала мигрируйте с Dart 2.19. - Migrating to null safety —
dart pub outdated --mode=null-safety,dart migrate, маркеры-подсказки, порядок зависимостей снизу вверх, Dart 2.19.6 как последний SDK с инструментом. - Flutter breaking changes and migration guides — список удалённых и изменённых API по версиям.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.