Ответ
В 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 экономит много времени.