Освоение навигации и маршрутизации в Flutter: полное руководство

Освоение навигации и маршрутизации в Flutter: полное руководство

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Введение

Введение

Навигация и маршрутизация являются фундаментальными понятиями в Flutter, которые позволяют пользователям перемещаться между разными экранами (страницами) в приложении. Хотя они связаны между собой, они служат разным целям.

В этом руководстве мы рассмотрим:

  1. Навигация vs Маршрутизация
  2. Императивная Навигация
  3. Декларативная Навигация
  4. Когда использовать каждый подход

1. Навигация vs Маршрутизация: понимание разницы

Навигация относится к процессу перемещения между экранами (например, с экрана входа на домашний экран). Это связано с действием перехода из одного представления в другое.

Маршрутизация - это механизм, который определяет, как должна происходить навигация, включая сопоставление путей, переходы и правила, регулирующие соединения экранов.

Простой аналог:

  • Навигация = "Перемещение с Экрана А на Экран Б"
  • Маршрутизация = "Определение того, как Экран А соединяется с Экраном Б"

2. Императивная Навигация: прямой контроль

Императивная навигация - это традиционный подход, при котором вы явно указываете приложению, как перемещаться, используя прямые команды (например, push, pop).

Как это работает

Вы вручную управляете стеком навигации с помощью класса Navigator Flutter,推ив и выталкивая маршруты по мере необходимости.

Преимущества:

  • Простой и понятный для базовой навигации
  • Точный контроль над стеком навигации
  • Нет дополнительных зависимостей

Недостатки:

  • Труднее поддерживать в сложных приложениях
  • Логика навигации распределена по виджетам
  • Труднее обрабатывать глубокие ссылки

Необходимые команды навигации:

Базовая навигация

// Добавить новый экран в стек
Navigator.push(
контекст,
MaterialPageRoute(builder: (контекст) => СледующийЭкран()),
);

// Вернуться на предыдущий экран
Navigator.pop(контекст);

Именованные маршруты

// Перейти к предопределенному именованному маршруту

// Переходит к именованному маршруту
Navigator.pushNamed(контекст, '/подробности');

// Заменить текущий маршрут именованным маршрутом
Navigator.pushReplacementNamed(контекст, '/домой');

// Удалить текущий, затем вновь добавить именованный маршрут
Navigator.popAndPushNamed(контекст, '/профиль');

// Добавить именованный маршрут и очистить стек
Navigator.pushNamedAndRemoveUntil(
контекст,
'/панель_управления',
(маршрут) => ложь
);

Управление стеком (удаление маршрута)

// Очистить весь стек и перейти на HomeScreen
Navigator.pushAndRemoveUntil(
контекст,
MaterialPageRoute(builder: (контекст) => HomeScreen()),
(маршрут) => ложь, // Удаляет ВСЕ маршруты
);

// Сохранить первый маршрут (например, после входа в систему)
Navigator.pushAndRemoveUntil(
контекст,
MaterialPageRoute(builder: (контекст) => Dashboard()),
(маршрут) => маршрут.isFirst,
);

// Заменить текущий экран
Navigator.pushReplacement(
контекст,
MaterialPageRoute(builder: (контекст) => НовыйЭкран()),
);

// Удалить определенный маршрут
Navigator.removeRoute(контекст, ModalRoute.of(контекст)!);

// Удалить маршрут ниже текущего
Navigator.removeRouteBelow(контекст, ModalRoute.of(контекст)!);

// Удалить до условия
Navigator.popUntil(контекст, (маршрут) => маршрут.isFirst);

Проверки навигации

// Проверить, возможен ли возврат
если (Navigator.canPop(контекст)) {
Navigator.pop(контекст);
}

// Умный возврат, который выполняется только при возможности
окончательный didPop = ожидание Navigator.maybePop(контекст);
если (!didPop) {
// Не могу вернуться - возможно выйти из приложения или показать предупреждение
}

// Проверить, использует ли пользователь жест восходящего движения
если (Navigator.of(контекст).userGestureInProgress) {
// Обработать жест возврата
}

3. Декларативная навигация: современный подход

Декларативная навигация управляет состоянием навигации как частью состояния приложения, что делает ее более предсказуемой и легкой в обслуживании в сложных приложениях.

Как это работает

Вы определяете маршруты и их состояния декларативно, а навигация управляется изменениями состояний приложения (как изменения URL-адресов в веб-приложениях).

Преимущества:

  • Лучше для сложных сценариев навигации
  • Централизованное и предсказуемое состояние навигации
  • Отличная глубокая связь поддержка
  • Отлично работает с решениями управления состоянием

Недостатки:

  • Требуется немного больше настроек
  • Может быть слишком для простых приложений

Типы декларативной навигации:

1. Маршрутизация на основе URL (с помощью go_router)

Самый распространенный подход, идеальный для веба и глубокой связи:

MaterialApp.router(
routerConfig: GoRouter(
routes: [
GoRoute(
path: '/',
builder: (_, __) => HomeScreen(),
),
GoRoute(
path: '/профиль/:id',
builder: (_, состояние) => ProfileScreen(id: состояние.pathParameters['id']!),
),
],
),
)

Когда использовать:

  • Веб-приложения
  • Приложения, требующие глубокие ссылки
  • Общие конфигурации маршрутов

2. Маршрутизация, управляемая состоянием

Маршруты меняются в зависимости от состояния приложения (например, изменений аутентификации):

MaterialApp(
home: BlocBuilder<AuthBloc>(
builder: (context, state) =>
state.isLoggedIn? HomeScreen() : LoginScreen(),
),
)

другой пример:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth_cubit.dart';
import 'screens/login_screen.dart';
import 'screens/home_screen.dart';
import 'screens/splash_screen.dart';

GoRouter createRouter(AuthCubit authCubit) {
return GoRouter(
initialLocation: '/splash',
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final authState = authCubit.state;

if (authState is Authenticated && state.location == '/login') {
return '/home';
}

if (authState is Unauthenticated && state.location!= '/login') {
return '/login';
}

return null;
},
routes: [
GoRoute(path: '/splash', builder: (_, __) => const SplashScreen()),
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),
],
);
}

Когда использовать:

  • Потоки аутентификации
  • Экраны настройки
  • Флаги функций

3. Вложенная навигация

Вложенная навигация означает управление независимыми стеками навигации в разных разделах вашего приложения:

Router(
routerDelegate: BeamerDelegate(
locationBuilder: (_, state) {
if (state.uri.path.contains('/dashboard')) {
return DashboardLocation(state);
}
return HomeLocation(state);
},
),
)

Когда использовать:

  • Приложения с нижней навигацией
  • Макеты с разделенным видом
  • Сложные приложения с несколькими потоками

Популярные пакеты декларативной навигации:

go_router

Лучше всего для: Большинства производственных приложений
Ключевые функции:

  • Автоматическая синхронизация URL
  • Встроенная поддержка глубоких ссылок
  • Простые охранники маршрутов через перенаправление

beamer

Лучше всего для: Приложений со сложной вложенной навигацией
Ключевые функции:

  • Группировка маршрутов с BeamLocation
  • Независимые стэки навигации
  • Встроенная настройка переходов

auto_route

Лучше всего подходит для: Большие приложения
Ключевые особенности:

  • Сгенерированные кодом типизированные маршруты
  • Сильная безопасность во время компиляции
  • Уменьшение количества шаблонного кода для сложных маршрутов

Ключевые декларативные шаблоны:

Охранники маршрутов

GoRouter(
redirect: (_, state) {
final loggedIn = auth.isLoggedIn;
if (!loggedIn &&!state.matchedLocation.startsWith('/login')) {
return '/login';
}
return null;
},
)

Динамические пути

GoRoute(
path: '/user/:id/posts/:postId',
builder: (_, state) => PostDetail(
userId: state.pathParameters['id']!,
postId: state.pathParameters['postId']!,
),
)

Пользовательские переходы

CustomTransitionPage(
child: DetailsScreen(),
transitionsBuilder: (_, animation, __, child) =>
FadeTransition(opacity: animation, child: child),
)

4. Выбор правильного подхода

Используйте императивную навигацию, когда:

  • Ваше приложение имеет простые потребности в навигации
  • Вы строите быстрый прототип
  • Вы предпочитаете прямой контроль над стэком навигации
  • Вам не нужна поддержка веба или глубокая связь поддержка

Используйте декларативную навигацию, когда:

  • Ваше приложение имеет сложные требования к навигации
  • Вам нужна поддержка веба
  • Вам нужны возможности глубокой связи
  • Ваша навигация зависит от состояния приложения (авторизация, разрешения)
  • Вы работаете над командным проектом, который пользуется строгими контрактами маршрутов

Вывод

Flutter предлагает мощные инструменты как для императивной, так и для декларативной навигации. Для простых приложений императивная навигация может быть все, что вам нужно. Но когда ваше приложение растет в сложности, декларативная навигация с пакетами, такими как go_router может сэкономить вам бесчисленные часы головных болей при обслуживании.

Помните:

  • Начинайте с Navigator.push/pop для базовых потребностей
  • Переходите к декларативному маршрутизированию по мере роста вашего приложения
  • Рассмотрите ваши веб-потребности и глубокую связь требования рано
  • Выбирайте шаблоны, соответствующие рабочему процессу вашей команды

Какой бы подход вы ни выбрали, понимание как императивной, так и декларативной навигации сделает вас более универсальным разработчиком Flutter. Счастливой навигации!

Report Page