Какие способы навигации в Flutter вы знаете?

«Какие способы навигации в Flutter вы знаете?» — вопрос из категории Навигация, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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