Навигация между экранами и стек маршрутов

Навигатор 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 назад. Понимание стека и передачи данных в обе стороны покрывает большинство переходов. Для сложных маршрутов и веб-ссылок есть декларативный роутинг.

Проверьте себя
1. Как устроена навигация во Flutter?
AЭкраны хранятся в очереди (FIFO)
BЭкраны лежат в стеке: push кладёт сверху, pop снимает верхний
CВсе экраны показываются одновременно
DНавигация невозможна без Riverpod
2. Как вернуть данные с экрана обратно на предыдущий?
AЧерез глобальную переменную
BВторым аргументом Navigator.pop(context, результат), получая его через await push
CЭто невозможно
DЧерез initState