Навигация во Flutter: как открыть заказ из пуша, не переписывая экраны
Переходы между экранами во Flutter выносят из виджетов в единый роутер: пакет go_router или auto_route хранит таблицу маршрутов, проверяет авторизацию в одном месте и открывает экран по ссылке из пуша. Экраны вызывают только context.go с путём и не знают друг о друге. Смена сценария правится в одном файле, а не в каждом экране.
🧭 Почему возникает путаница с переходами во Flutter
Торговый представитель получает уведомление «Заказ готов к отгрузке» и нажимает на него. Приложение открывается на главном экране, а не на заказе. Менеджер просит разработчика связать пуш с карточкой заказа. Разработчик открывает код и находит Navigator.push с MaterialPageRoute прямо в обработчике кнопки на экране списка. Из пуша этот вызов не повторить: ему нужен BuildContext конкретного экрана, а экрана на тот момент ещё нет.
С такой схемы начинают многие Flutter-проекты. Экран списка импортирует экран карточки и сам решает, куда идти дальше. Это удобно, пока экранов пять. Но каждый новый вход в приложение (пуш, ссылка из письма, QR-код на накладной) требует собрать стек вручную: открыть список, потом карточку и не забыть проверить, вошёл ли пользователь.
Из этой схемы вырастают четыре проблемы:
- ✓Экраны сцеплены импортами. Чтобы вставить шаг подтверждения между списком и карточкой, правят оба экрана и всё, что открывает карточку.
- ✓Проверка авторизации разбросана по обработчикам. В одном месте она есть, в другом забыта, поэтому ссылка из письма открывает закрытый раздел без входа.
- ✓После await в асинхронном обработчике контекст может уже не существовать. Анализатор ругается на use_build_context_synchronously, а на устройстве переход срабатывает через раз.
- ✓Переходы проверяют только нажатием кнопок в виджет-тестах, поэтому маршруты почти никто не тестирует.
Цена вопроса растёт вместе с приложением. Каждый новый сценарий входа (код из СМС вместо пароля, подтверждение номера) превращается в поиск по всему проекту. Релиз сдвигается, пока сборка снова проходит модерацию в магазинах, а представители в поле ищут заказы вручную. Пуш, который не ведёт к нужному экрану, пользователи перестают нажимать, и уведомления перестают работать как канал.
🔧 Как исправить: роутер вместо Navigator.push в экранах
Идея в том, что экран не выбирает следующий экран. Он сообщает «открой заказ 42», а путь, параметры, проверку входа и стек собирает роутер. Разберём на go_router: пакет опубликован командой Flutter и закрывает большинство бизнес-приложений.
Таблица маршрутов в одном файле
Каждый маршрут описывается путём вроде /orders/:id и функцией, которая строит экран. Вложенные маршруты задают стек: карточка /orders/42 лежит внутри /orders, поэтому после перехода из пуша кнопка «назад» ведёт на список заказов, а не закрывает приложение.
Вызовы вместо Navigator.push
В экране остаётся одна строка. Вызов context.go('/orders/42') переходит по пути и выстраивает стек по таблице, а context.push('/contractors/pick') кладёт временный экран поверх текущего. Если строковые пути надоели, пакет go_router_builder генерирует типизированные маршруты: опечатка в пути ломает сборку, а не работу у клиента.
Авторизация в redirect
Функция redirect срабатывает при каждом переходе. Гостя она отправляет на /login и запоминает исходный путь, а после входа возвращает на него. Сценарий с пушем проходит сам: нажал на уведомление, ввёл пароль, увидел карточку заказа. Чтобы роутер пересчитывал правила при выходе из аккаунта, ему передают refreshListenable со ссылкой на состояние авторизации.
Пуш и внешние ссылки
Уведомление несёт путь в своих данных, обработчик передаёт его в router.go, и приложение открывается на нужном экране. Для закрытого приложения путь читают при запуске. Для ссылок из браузера и писем дополнительно настраивают Android App Links и Universal Links на iOS, но разбор самого пути уже сделан роутером.
📊 Какой пакет выбрать: go_router, auto_route или Navigator
Подходов четыре. Таблица показывает, где каждый начинает мешать.
| Подход | Где лежат переходы | Ссылки из пуша | Проверка входа | Когда брать |
|---|---|---|---|---|
| Navigator.push в экранах | В обработчиках кнопок | Стек собирают вручную | Повторяется в каждом экране | Прототип на несколько экранов |
| Именованные маршруты | Таблица в одном месте, вызовы по строкам | Путь с параметрами разбирают своим кодом | Пишут сами в onGenerateRoute | Небольшое приложение без внешних ссылок |
| go_router | Таблица маршрутов в одном файле | Работают из коробки | Функция redirect | Большинство бизнес-приложений |
| auto_route | Аннотации у экранов, маршруты генерируются | Поддерживаются | Guards | Крупный проект со строго типизированными аргументами |
Для типового B2B-приложения (каталог, заказы, сканирование штрихкодов, профиль) мы берём go_router. Если команда уже пишет на auto_route и всё работает, переходить на другой пакет незачем: главное, чтобы переходы жили в роутере, а не в экранах. Собственный Router API с RouterDelegate оправдан редко, например при нестандартной вложенности вкладок.
🔁 Что делать, если ошибка повторяется после рефакторинга
Роутер убирает целый класс проблем, но не все. Вот что возвращается чаще всего.
- ✓Ошибка «Navigator operation requested with a context that does not include a Navigator» появляется, когда контекст взят выше MaterialApp.router, например в main или при старте до построения дерева. Вызывайте методы экземпляра роутера напрямую, без контекста.
- ✓Предупреждение use_build_context_synchronously значит, что после await переход вызывается с контекстом, которого могло уже не быть. Перед переходом ставят проверку if (!context.mounted) return; или берут ссылку на роутер до await.
- ✓Стек удваивается, а «назад» ведёт не туда. Обычно в проекте перемешаны push и go. Договоритесь: go для переходов между разделами, push только для временных экранов поверх.
- ✓Функция redirect уходит в цикл: с /login правило снова отправляет на /login. В начале функции проверяйте, что пользователь уже на целевом пути. go_router оборвёт цепочку по лимиту перенаправлений и покажет ошибку, но причину придётся искать по логам.
- ✓Экран открывается дважды при двойном нажатии. Блокируйте кнопку на время запроса или перехода.
Если ошибки остаются и после этого, дело чаще в архитектуре, чем в роутере: бизнес-логика вызывает переходы напрямую, в обход состояния. Пусть логика сообщает результат («заказ создан»), а слушатель состояния на уровне экрана вызывает роутер. Тогда логику можно тестировать без единого экрана.
🛡️ Как предотвратить возврат проблемы: правила для команды
- ✓Запретите Navigator.push и MaterialPageRoute в папке с экранами. Проверка поиском в CI занимает несколько строк скрипта.
- ✓Покройте тестом функцию redirect: гость уходит на /login, вошедший попадает на запрошенный путь, пользователь без прав на раздел получает отказ.
- ✓Ведите таблицу путей как документ. Команда, которая отправляет пуши, и разработчики приложения пользуются одним списком.
- ✓Включите правило use_build_context_synchronously в analysis_options.yaml, если его там нет.
- ✓Добавляйте новый экран вместе с маршрутом и тестом на открытие по пути в одном запросе на слияние.
💼 Как мы строим навигацию в проектах и сколько это стоит
Мы делаем мобильные приложения на Flutter для малого и среднего бизнеса в Москве: приложения для торговых представителей, складов, курьеров и клиентские кабинеты. Роутер ставим в самом начале проекта, до первого экрана с логикой.
Новое приложение начинается с таблицы путей, которую мы согласуем с теми, кто отправляет уведомления. Если данные живут в 1С, статус заказа меняется там, а уведомление с путём /orders/{номер} уходит в приложение, и оно открывает нужную карточку. Как приложение связано с учётной системой, описано на страницах про B2B-приложение с 1С и интеграцию приложения с 1С.
Заказчик получает таблицу путей как документ и тесты на redirect в составе кода. Следующий подрядчик или штатный разработчик не начинает с нуля.
Разработка мобильных приложений у нас стоит от 500 000 ₽. Итоговая цена зависит от числа экранов, ролей и объёма обмена с 1С, поэтому точную сумму мы называем после разбора задачи. Если приложение уже есть и переходы зашиты в экраны, начинаем с аудита кода: находим все вызовы Navigator, собираем таблицу путей и переносим раздел за разделом, не останавливая выпуск версий. Стоимость переноса называем после аудита, потому что она зависит от числа вызовов и от связи бизнес-логики с экранами.
❓ Частые вопросы
Что лучше для навигации во Flutter: go_router или Navigator.push?
Для приложения, где больше нескольких экранов, лучше go_router или auto_route: переходы лежат в одной таблице, авторизация проверяется в redirect, а ссылки из пушей работают сразу. Navigator.push в обработчиках кнопок годится для прототипа. Когда в приложении появляются пуши и роли, такие переходы приходится переписывать.
Как открыть нужный экран по пуш-уведомлению во Flutter?
Уведомление передаёт в данных путь, например /orders/42. Обработчик нажатия на пуш вызывает router.go с этим путём. Для закрытого приложения путь читают при запуске. Если пользователь не вошёл, redirect сначала покажет экран входа, а после авторизации вернёт его на запрошенный заказ без дополнительного кода в экранах.
Почему во Flutter появляется use_build_context_synchronously при переходе после await?
Предупреждение значит, что после await вы вызываете переход с контекстом, который мог исчезнуть: экран уже закрыт или пересобран. Перед переходом добавьте проверку if (!context.mounted) return; либо возьмите ссылку на роутер до await. Включите это правило в analysis_options.yaml, чтобы такие места находились до релиза.
Можно ли перенести навигацию в готовом приложении без остановки разработки?
Да. Сначала проводим аудит: находим все вызовы Navigator.push и составляем таблицу путей. Затем переносим разделы по одному в роутер, а выпуск версий продолжается. Старые и новые переходы какое-то время работают параллельно, поэтому приложение остаётся рабочим на каждом шаге. Стоимость называем после аудита кода.
Сколько стоит разработка мобильного приложения на Flutter?
Разработка мобильного приложения у нас стоит от 500 000 ₽. Итоговая цена зависит от числа экранов, ролей пользователей и объёма обмена данными с 1С. Точную стоимость мы называем после разбора задачи, когда понятно, какие процессы приложение закрывает и какие данные берёт из учётной системы.
Или позвоните: +7 906 045-28-27 — в рабочее время с 9:00 до 19:00
Остались вопросы? Нужна помощь?
Менеджеры компании с радостью ответят на ваши вопросы, произведут расчет стоимости услуг и подготовят индивидуальное коммерческое предложение.
Бесплатная консультация

