Навигация между экранами и стек маршрутов
Навигатор Flutter работает как стопка карт: новый экран кладётся сверху, кнопка «назад» снимает верхний.
Суть:
Navigatorхранит экраны в стеке маршрутов.pushдобавляет экран сверху,popубирает верхний. Так реализуется переход вперёд и возврат назад.
Стоит различать push и pushReplacement. Первый кладёт экран поверх стека, сохраняя предыдущий для возврата; второй заменяет текущий экран новым, так что назад вернуться уже нельзя. Замена незаменима после входа в аккаунт: показав главный экран через pushReplacement, вы не позволите пользователю кнопкой «назад» вернуться на форму логина. Понимание этих нюансов делает навигацию предсказуемой.
Приложение из одного экрана — редкость. Список открывает деталь, деталь — форму редактирования. Flutter управляет переходами через Navigator, который работает по принципу стека (LIFO: последним пришёл — первым ушёл). Когда вы открываете экран, он ложится на вершину стопки; когда нажимаете «назад», верхний экран снимается, и показывается предыдущий.
// открыть новый экран
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailScreen()),
);
// вернуться назад
Navigator.pop(context);
// передать данные на новый экран — через конструктор
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(itemId: 42),
),
);
// вернуть данные назад
Navigator.pop(context, 'результат');
Данные вперёд передают через конструктор целевого экрана. Данные назад — вторым аргументом pop; вызывающая сторона получает их, ожидая результат await Navigator.push(...).
Как работает стек под капотом
Navigator хранит список маршрутов (routes). push добавляет маршрут и анимирует появление нового экрана. pop удаляет верхний и анимирует возврат. Системная кнопка «назад» на Android по умолчанию вызывает pop. Когда стек пуст, приложение закрывается. Понимание стека объясняет, почему «назад» возвращает именно на предыдущий экран, а не куда попало.
Стек маршрутов (вершина сверху)
push(Detail) push(Edit) pop()
+----------+ +----------+ +----------+
| Detail | <-- | Edit | --> | Detail |
+----------+ +----------+ +----------+
| Home | | Detail | | Home |
+----------+ +----------+ +----------+
| Home |
+----------+
видно всегда верхний экран; pop снимает его
# Модель стека навигации: push кладёт сверху, pop снимает
class Navigator:
def __init__(self):
self.stack = ['Home']
def push(self, screen):
self.stack.append(screen)
print('push ->', self.stack, '| виден:', self.stack[-1])
def pop(self, result=None):
left = self.stack.pop()
msg = f' (вернул: {result})' if result else ''
print('pop ->', self.stack, '| виден:', self.stack[-1], msg)
nav = Navigator()
nav.push('Detail')
nav.push('Edit')
nav.pop('сохранено') # вернулись на Detail с результатом
nav.pop() # вернулись на Home
Частые ошибки
popна последнем экране — стек опустеет и приложение закроется; проверяйтеNavigator.canPop(context).- Ждать результат без
await— данные, возвращённые черезpop, не дойдут. - Передавать тяжёлые данные через конструктор вместо идентификатора — лучше передать id и загрузить данные на новом экране.
Best practices
- Для возврата результата используйте
final result = await Navigator.push(...). - Передавайте вперёд минимум данных (id), а не целые объекты.
- Для крупных приложений с глубокими ссылками рассмотрите
go_router(следующий урок).
Когда нужно сбросить навигацию полностью — например, после выхода из аккаунта вернуть на стартовый экран и стереть всю историю — пригодится pushAndRemoveUntil. Он кладёт новый экран и удаляет все предыдущие по заданному условию. Все эти методы — это операции над одной и той же стопкой маршрутов, поэтому, твёрдо усвоив модель стека, вы легко разберётесь с любым из них, лишь взглянув на название.
Итог: навигация Flutter — это стек экранов: push вперёд, pop назад. Понимание стека и передачи данных в обе стороны покрывает большинство переходов. Для сложных маршрутов и веб-ссылок есть декларативный роутинг.