Привет, Хабр! Меня зовут Юрий Петров, я автор ютуб-канала «Мобильный разработчик» и Flutter Team Lead в Friflex. Мы разрабатываем мобильные приложения для бизнеса и специализируемся на Flutter.
В этом руководстве я хочу рассказать про пакет go_router. Он помогает управлять навигацией во Flutter. Команда разработки Flutter поддерживает данный пакет. Это позволяет надеяться, что в дальнейшем пакет продолжит развиваться.
Рассказывать буду на примере простого проекта — Todo (заметки). Я понимаю, что таких проектов на Хабре очень много. Но, по-моему, лучшего примера не найти.

Создаем проект и подключаем библиотеку
Немного подумав, я решил, что для нашего проекта необходимо как минимум шесть экранов.
-
Экран аутентификации пользователя
AuthScreen. Будем использовать фейковую аутентификацию. -
Экран со списком заметок —
NotesScreen. -
Экран с детальной информацией заметок —
DetailNoteScreen. -
Экран с любимыми заметками —
FavoriteNotesScreen. -
Экран для создания заметок —
CreateNoteScreen. -
Экран для управления профилем —
ProfileScreen. -
Экран для размещения рутового экрана с нижней навигационной панелью —
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. А в параметре branches — StatefulShellBranch с нужными маршрутами.
Теперь создадим навигационную панель на экране 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/
Добавить комментарий