Расскажи про опыт работы с навигацией во Flutter

Ответ

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

1. Императивная навигация (Navigator 1.0): Использую для простых приложений или внутри отдельных модулей. Быстро и понятно.

// Переход с передачей данных
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailsScreen(item: selectedItem),
  ),
);

// Возврат с результатом
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionScreen()),
);
if (result != null) { /* обработка результата */ }

2. Именованные маршруты (Named Routes): Удобны для централизованного управления маршрутами в MaterialApp.

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/details': (context) => DetailsScreen(),
    '/profile': (context) => ProfileScreen(),
  },
  onGenerateRoute: (settings) {
    // Динамическая генерация маршрута, например, для '/user/:id'
    if (settings.name?.startsWith('/user/') ?? false) {
      final userId = settings.name!.split('/').last;
      return MaterialPageRoute(
        builder: (context) => UserScreen(userId: userId),
      );
    }
    return null;
  },
);

3. Декларативная навигация (Navigator 2.0 / Router API): Применял в проектах с глубокими ссылками, веб-поддержкой и сложным состоянием навигации (например, приложение с вкладками и модальными окнами). Использовал пакет go_router, который сильно упрощает работу с Router API.

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id', // Глубокие ссылки /details/123
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return DetailsScreen(itemId: id);
          },
        ),
      ],
    ),
  ],
  redirect: (context, state) {
    // Логика редиректа, например, проверка авторизации
    final isLoggedIn = authService.isLoggedIn;
    final goingToLogin = state.location == '/login';
    if (!isLoggedIn && !goingToLogin) return '/login';
    if (isLoggedIn && goingToLogin) return '/';
    return null;
  },
);
// В MaterialApp
MaterialApp.router(
  routerConfig: _router,
)

4. Управление состоянием навигации: Интегрировал навигацию с менеджером состояния (например, Riverpod). Состояние приложения (выбранный элемент, фильтры) определяет текущий стек страниц, что делает логику предсказуемой и тестируемой.

Опыт включает: реализацию глубоких ссылок для пуш-уведомлений, кастомные переходы с PageRouteBuilder, защиту маршрутов (guards) и сохранение состояния при навигации с AutomaticKeepAliveClientMixin.

Ответ 18+ 🔞

Слушай, давай я тебе про навигацию во Flutter расскажу, как есть. Работал с этим всем, от простого к сложному, и могу сказать — выбор подхода это, блядь, как выбор оружия в хайпе: от кухонного ножа до ракетной установки, зависит от того, кого режешь — бутерброд или целую армию.

1. Императивная навигация (Старый добрый Navigator 1.0): Это как поехать на картошку на старой «копейке» — едет, гремит, но довезёт, ёпта. Беру, когда приложение проще пареной репы или внутри какого-нибудь модуля, где не надо мозги выносить.

// Тыкаешь кнопку — летишь на другой экран, прихватив с собой данные
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailsScreen(item: selectedItem),
  ),
);

// А тут ещё и ответа ждёшь, как дурак у телефона
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionScreen()),
);
if (result != null) { /* ну и что нам прилетело? */ }

Просто, понятно, но когда маршрутов становится овердохуища, начинается пиздец и бардак. Все эти push и pop раскиданы по коду, как говно мамонта по пещере.

2. Именованные маршруты (Named Routes): Тут уже начинается порядок, блядь. Как будто разложил инструменты по полочкам в гараже. Всё красиво объявляешь в MaterialApp.

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/details': (context) => DetailsScreen(),
    '/profile': (context) => ProfileScreen(),
  },
  onGenerateRoute: (settings) {
    // А это уже для крутых динамических штук, типа '/user/:id'
    if (settings.name?.startsWith('/user/') ?? false) {
      final userId = settings.name!.split('/').last; // Выковыриваем айдишник
      return MaterialPageRoute(
        builder: (context) => UserScreen(userId: userId),
      );
    }
    return null; // Не наш маршрут — иди нахуй
  },
);

Уже лучше, централизованно. Но как только начинаешь лезть в глубокие ссылки, авторизацию или сложные стеки — чувствуешь, что это, блядь, как пытаться забить гвоздь микроскопом. Неудобно.

3. Декларативная навигация (Navigator 2.0 / Router API): Вот тут, сука, начинается магия для взрослых. Берёшь, когда проект не игрушечный: нужны глубокие ссылки, веб-поддержка, состояние навигации сложнее, чем «вперёд-назад». Сам Router API — это такой зверь, что с ним можно сойти с ума. Поэтому все умные люди берут go_router. Это как взять тот же микроскоп, но приделать к нему автомат Калашникова для удобства.

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
      routes: [
        GoRoute(
          path: 'details/:id', // Глубокие ссылки типа /details/123 работают из коробки
          builder: (context, state) {
            final id = state.pathParameters['id']!; // Достаём айди как конфетку
            return DetailsScreen(itemId: id);
          },
        ),
      ],
    ),
  ],
  redirect: (context, state) {
    // А это охрана, блядь! Логика редиректов. Не пустит куда не надо.
    final isLoggedIn = authService.isLoggedIn;
    final goingToLogin = state.location == '/login';
    if (!isLoggedIn && !goingToLogin) return '/login'; // Не авторизован? На логин!
    if (isLoggedIn && goingToLogin) return '/'; // Уже вошёл? Иди на главную!
    return null; // Всё ок, проходи
  },
);
// И подключаешь эту красоту
MaterialApp.router(
  routerConfig: _router,
)

Вот это, ёпта, сила. Всё декларативно, логика навигации отдельно от UI, глубокие ссылки, охрана маршрутов — красота.

4. Управление состоянием навигации: А это высший пилотаж, когда ты навигацию впрягаешь в свою систему управления состоянием, например, в Riverpod. Получается, что состояние приложения (что выбрал пользователь, какие фильтры включил) само диктует, какой стек экранов ему показывать. Это как если бы твоя машина сама выбирала дорогу, исходя из того, куда ты действительно хочешь попасть, а не куда ты ткнул на карте. Предсказуемо и тестируемо до безумия.

Короче, опыт включает: делал глубокие ссылки, чтобы пуш-уведомления открывали нужный экран, а не просто приложение. Городил кастомные анимации переходов через PageRouteBuilder, когда стандартные не канают. Ставил охрану на маршруты, чтобы всякие пидарасы шерстяные не лазили куда не следует. И боролся за сохранение состояния экранов, чтобы при возврате назад всё не сбрасывалось в ноль, используя AutomaticKeepAliveClientMixin — без этого, блядь, пользователи готовы были меня растерзать.