Полное руководство по управлению навигацией во Flutter с помощью пакета go_router: часть 1

от автора

Привет, Хабр! Меня зовут Юрий Петров, я автор ютуб-канала «Мобильный разработчик» и Flutter Team Lead в Friflex. Мы разрабатываем мобильные приложения для бизнеса и специализируемся на Flutter. 

В этом руководстве я хочу рассказать про пакет go_router. Он помогает управлять навигацией во Flutter. Команда разработки Flutter поддерживает данный пакет. Это позволяет надеяться, что в дальнейшем пакет продолжит развиваться.

Рассказывать буду на примере простого проекта — Todo (заметки). Я понимаю, что таких проектов на Хабре очень много. Но, по-моему, лучшего примера не найти.

Создаем проект и подключаем библиотеку

Немного подумав, я решил, что для нашего проекта необходимо как минимум шесть экранов.

  1. Экран аутентификации пользователя AuthScreen. Будем использовать фейковую аутентификацию.

  2. Экран со списком заметок — NotesScreen.

  3. Экран с детальной информацией заметок — DetailNoteScreen.

  4. Экран с любимыми заметками — FavoriteNotesScreen.

  5. Экран для создания заметок — CreateNoteScreen.

  6. Экран для управления профилем — ProfileScreen.

  7. Экран для размещения рутового экрана с нижней навигационной панелью — RootScreen.

Для удобства создаем четыре папки в папке features. В каждой из них — экраны. В каждом файле создаем одноименный StatelessWidget.

Реализация нижней навигационной панели — BottomNavigationBar

Создаем папку routing, в которой будем хранить карту маршрутов. Для создания нижней навигационной панели будем использовать маршрут StatefulShellRoute. Этот маршрут отображает пользовательский интерфейс с отдельными навигаторами для всех его под маршрутов. Он работает как ShellRoute, но с одним исключением. Он создает отдельные навигаторы для каждой из своих вложенных ветвей, то есть, параллельных деревьев навигации.

С StatefulShellRoute мы можем разрабатывать приложения с сохранением состояния вложенной навигации. Это удобно, например, при создании интерфейса с BottomNavigationBar, где для каждой вкладки сохраняется свое состояние навигации.

Чтобы создать StatefulShellRoute, указываем список элементов StatefulShellBranch. Каждый из них представляет отдельную ветку маршрута с сохранением состояния. 

StatefulShellBranch предоставляет корневые маршруты и ключ навигатора (GlobalKey) для ветки, а также начальное расположение. Как и в случае с ShellRoute, при создании StatefulShellRoute нужно предоставить либо builder, либо pageBuilder

Эти конструкторы отличаются тем, что они принимают параметр StatefulNavigationShell вместо дочернего виджета. StatefulNavigationShell можно использовать, чтобы получить доступ к информации о состоянии маршрута или переключить активную ветку. То есть, для восстановления стека навигации другой ветки. Это делается с помощью метода StatefulNavigationShell.goBranch.

routing/app_routing.dart
final router = GoRouter(   initialLocation: '/notes',   routes: [     // BottomNavigationBar     StatefulShellRoute.indexedStack(       builder: (context, state, navigationShell) =>           RootScreen(navigationShell: navigationShell),       branches: [         StatefulShellBranch(           routes: [             GoRoute(               path: '/notes',               builder: (context, state) => const NotesScreen(),             ),           ],         ),         StatefulShellBranch(           routes: [             GoRoute(               path: '/favorites',               builder: (context, state) => const FavoritesScreen(),             ),           ],         ),         StatefulShellBranch(           routes: [             GoRoute(               path: '/profile',               builder: (context, state) => const ProfileScreen(),             ),           ],         ),       ],     ),   ], );

Объявляем глобальный router. Для удобства будем применять StatefulShellRoute с конструктором indexedStack. В параметре builder указываем RootScreen и передаем в него navigationShell. А в параметре branchesStatefulShellBranch с нужными маршрутами.

Теперь создадим навигационную панель на экране RootScreen. Для этого используем полученный navigationShell. Из него получаем текущий индекс и маршрут, который указан в ветви маршрута.

root_screen.dart
class RootScreen extends StatelessWidget {   const RootScreen({super.key, required this.navigationShell});    /// Контейнер для навигационного бара.   final StatefulNavigationShell navigationShell;    @override   Widget build(BuildContext context) {     return Scaffold(       body: navigationShell,       bottomNavigationBar: BottomNavigationBar(         /// Лист элементов для нижнего навигационного бара.         items: _buildBottomNavBarItems,         /// Текущий индекс нижнего навигационного бара.         currentIndex: navigationShell.currentIndex,         /// Обработчик нажатия на элемент нижнего навигационного бара.         onTap: (index) => navigationShell.goBranch(           index,           initialLocation: index == navigationShell.currentIndex,         ),       ),     );   }    // Возвращает лист элементов для нижнего навигационного бара.   List<BottomNavigationBarItem> get _buildBottomNavBarItems => [         const BottomNavigationBarItem(           icon: Icon(Icons.note),           label: 'Заметки',         ),         const BottomNavigationBarItem(           icon: Icon(Icons.favorite),           label: 'Любимые',         ),         const BottomNavigationBarItem(           icon: Icon(Icons.person),           label: 'Профиль',         ),        ]; }

Дальше нужно добавить в main.dart инициализацию роутера:

main.dart
void main() {   runApp(const AppTodo()); }  class AppTodo extends StatelessWidget {   const AppTodo({super.key});    @override   Widget build(BuildContext context) {     return MaterialApp.router(       routerConfig: router,     );   } }

Инициализируем роутер для приложения. У него есть доступ к контексту и механизм поиска нужного маршрута, поэтому дальше обращаться к методам роутера будем через context.

Создаем MaterialApp. Используем в нем go_router вместо Navigator.

Обратите внимание, что здесь мы не передаем корневой виджет. Теперь всей навигацией управляет go_router. При запуске приложения вы увидите корневой маршрут с вложенной навигацией.

Отлично! Нам осталось создать навигацию детальной информации о заметке.

Реализация маршрута детальной информации

Для этого в экране NotesScreen создадим моковый список заметок и нарисуем их. По клику мы переходим на экран с детальной информацией о заметке.

Обратите внимание! Переходя на этот экран, мы открываем маршрут, который находится внутри одной ветки маршрутов.

notes_screen.dart
/// Моковый список заметок const _notes = [   'Note 1',   'Note 2',   'Note 3',   'Note 4',   'Note 5',   'Note 6', ];  class NotesScreen extends StatelessWidget {   const NotesScreen({super.key});    @override   Widget build(BuildContext context) {     return Center(       child: ListView.separated(           itemBuilder: (context, index) {             return ListTile(               title: Text(_notes[index]),               onTap: () {                 // При клике на заметку переходим на экран                 // с детальной информацией                  context.go('/notes/detail');               },             );           },           separatorBuilder: (context, index) {             return const Divider();           },           itemCount: _notes.length),     );   } }

Осталось добавить этот маршрут в карту маршрутов ветки маршрута /notes.

  StatefulShellBranch(           routes: [             GoRoute(                 path: '/notes',                 builder: (context, state) => const NotesScreen(),                 routes: [                   GoRoute(                     path: 'detail',                     builder: (context, state) => const DetailScreen(),                   ),                 ]),           ],         ),

Теперь файл app_routing.dart выглядит так:

app_routing.dart
final router = GoRouter(   initialLocation: '/notes',   routes: [     // BottomNavigationBar     StatefulShellRoute.indexedStack(       builder: (context, state, navigationShell) =>           RootScreen(navigationShell: navigationShell),       branches: [         StatefulShellBranch(           routes: [             GoRoute(                 path: '/notes',                 builder: (context, state) => const NotesScreen(),                 routes: [                   GoRoute(                     path: 'detail',                     builder: (context, state) => const DetailScreen(),                   ),                 ]),           ],         ),         StatefulShellBranch(           routes: [             GoRoute(               path: '/favorites',               builder: (context, state) => const FavoritesScreen(),             ),           ],         ),         StatefulShellBranch(           routes: [             GoRoute(               path: '/profile',               builder: (context, state) => const ProfileScreen(),             ),           ],         ),       ],     ),   ], );

Запускаем приложение и получаем такой результат:

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

Пример из статьи можно посмотреть по ссылке.

Документация доступна на сайте.


ссылка на оригинал статьи https://habr.com/ru/articles/787528/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *