Ответ
В Flutter я работал с разными подходами к навигации, в зависимости от сложности приложения:
1. Императивная навигация (Navigator 1.0) — для простых случаев:
// Переход на новый экран
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(item: item),
settings: RouteSettings(name: '/details'),
),
);
// Возврат с передачей данных
Navigator.pop(context, resultData);
// Получение результата
final result = await Navigator.push(...);
if (result != null) {
// Обработка результата
}
2. Именованные маршруты — для средних проектов:
MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailsScreen(),
'/profile': (context) => ProfileScreen(),
},
initialRoute: '/',
);
// Использование
Navigator.pushNamed(context, '/details', arguments: itemId);
// Извлечение аргументов
final args = ModalRoute.of(context)!.settings.arguments;
3. onGenerateRoute — для динамических маршрутов:
MaterialApp(
onGenerateRoute: (settings) {
// Парсинг сложных путей
if (settings.name!.startsWith('/user/')) {
final userId = settings.name!.split('/').last;
return MaterialPageRoute(
builder: (context) => UserProfileScreen(userId: userId),
);
}
// Фабричный метод для экранов
switch (settings.name) {
case '/login':
return MaterialPageRoute(builder: (_) => LoginScreen());
case '/product/:id':
final id = (settings.arguments as Map)['id'];
return MaterialPageRoute(builder: (_) => ProductScreen(id: id));
}
return MaterialPageRoute(builder: (_) => NotFoundScreen());
},
);
4. GoRouter — мой выбор для production-приложений:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final id = state.params['id']!;
return DetailsScreen(id: id);
},
),
GoRoute(
path: 'profile',
builder: (context, state) => ProfileScreen(),
routes: [
GoRoute(
path: 'settings',
builder: (context, state) => SettingsScreen(),
),
],
),
],
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
redirect: (context, state) {
// Логика редиректов (например, проверка авторизации)
final isLoggedIn = authService.isLoggedIn;
if (!isLoggedIn && state.location != '/login') {
return '/login';
}
return null;
},
);
// Использование
context.go('/details/123');
context.push('/profile/settings');
5. Navigator 2.0 (Router API) — для полного контроля:
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
@override
Widget build(BuildContext context) {
return Navigator(
pages: [
MaterialPage(
key: ValueKey('home'),
child: HomeScreen(),
),
if (_showDetails)
MaterialPage(
key: ValueKey('details'),
child: DetailsScreen(),
),
],
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
_showDetails = false;
notifyListeners();
return true;
},
);
}
}
6. BottomNavigationBar с nested navigation:
// Каждая вкладка имеет свой Navigator
IndexedStack(
index: _currentIndex,
children: [
Navigator(
key: _homeKey,
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (_) => HomeTabScreen(),
),
),
Navigator(
key: _profileKey,
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (_) => ProfileTabScreen(),
),
),
],
);
В текущих проектах я использую GoRouter, так как он сочетает простоту именованных маршрутов с мощью Navigator 2.0: глубокие ссылки, редиректы, защищённые маршруты и вложенная навигация работают из коробки. Для очень простых приложений достаточно императивной навигации, но для чего-то серьёзного declarative-подход с GoRouter экономит много времени.
Ответ 18+ 🔞
Слушай, вот навигация во Flutter — это вообще отдельная песня, блядь. Как будто в магазин за хлебом идешь, а там тебе предлагают на выбор: пешком, на велосипеде, на такси, на вертолёте или через телепорт, ёпта. И каждый способ хорош, но для своей конкретной ситуации.
Вот смотри, если у тебя приложение — как та самая мартышлюшка, на трёх экранах максимум, то заморачиваться не надо. Просто пихаешь экраны друг на друга, как блины.
// Старый добрый способ, как в 2018 году
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(item: item),
),
);
Работает, понятно, но когда экранов становится овердохуища, начинается пиздец. Ты уже не помнишь, какой экран куда ведёт, deep links не работают, бэкстейк ломается. Чувствуешь себя полным ебанько.
Потом придумали именованные маршруты. Ну, вроде красиво, как адреса в интернете.
MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailsScreen(),
},
);
Но и тут подстава. Хочешь передать userId в /user/123? Приготовься к танцам с бубном и onGenerateRoute. Начинаешь парсить строки, как будто на дворе 2002-й год и ты пишешь свой веб-сервер. Чистый ад, ебать копать.
И вот тут на сцену выходит GoRouter. Это, блядь, как пересадка с запряжённой лошади на Tesla. Сначала смотришь на него и думаешь: «О, опять какая-то хуйня, сейчас опять месяц буду разбираться». Но нет, чувак. Оказалось, что это гениально просто.
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
routes: [
GoRoute(
path: 'details/:id', // Смотри, красота! Параметр прямо в пути
builder: (context, state) {
final id = state.params['id']!; // Достал и всё
return DetailsScreen(id: id);
},
),
],
),
],
);
И самое главное — redirect. Раньше, чтобы проверить авторизацию и перенаправить на логин, нужно было оборачивать каждый ебучий экран в StreamBuilder или FutureBuilder. Теперь это одна строчка в конфиге роутера. Волнение ебать прошло.
redirect: (context, state) {
final isLoggedIn = authService.isLoggedIn;
if (!isLoggedIn && state.location != '/login') {
return '/login'; // Не авторизован? Нахуй иди логиниться.
}
return null;
},
Конечно, есть и Navigator 2.0 — это уже для настоящих мазохистов, которые хотят контролировать абсолютно всё, вплоть до анимации перехода при смене времени суток. Сила, мощь, но и кода — хуй в пальто. Для 95% приложений это оверкилл.
Итог мой такой: если делаешь пет-проект на коленке — хватит и простого Navigator.push. Не выёбывайся. Если проект растёт и в нём больше пяти экранов — бери GoRouter, не ошибёшься. Он закрывает все базовые и не очень потребности: deep linking, protected routes, nested navigation. Всё из коробки, без костылей и велосипедов. А если чувствуешь себя богом Flutter'а и хочешь написать свой фреймворк поверх фреймворка — тогда да, welcome в мир RouterDelegate. Но потом не жалуйся, что жизнь — боль.