Руководства
Модульная навигация по вкладкам
NavigationBar с независимыми стеками Catalog и Profile, сохранением состояния и явным переключением ветвей.
Модульная навигация по вкладкам
Готовый Shell содержит Catalog и Profile. Откройте Product в Catalog, перейдите в Profile и вернитесь. Product останется открытым, потому что каждой вкладке принадлежит отдельный стек Navigator.
Итоговое дерево Scope
root
└── shell
├── catalog-tab [Catalog, Product]
└── profile-tab [Profile]
Задайте Scope ID
const rootScopeId = NavigationScopeId('root');
const shellScopeId = NavigationScopeId('shell');
const catalogScopeId = NavigationScopeId('catalog-tab');
const profileScopeId = NavigationScopeId('profile-tab');
Эти ID определяют конкретные стеки. Они не зависят от порядка вкладок в интерфейсе.
Экспортируйте Module функциональностей
final catalogModule = NavigationModule(
id: const NavigationModuleId('catalog'),
routes: <NavigationRouteDefinitionBase>[catalogRoute, productRoute],
);
final profileModule = NavigationModule(
id: const NavigationModuleId('profile'),
routes: <NavigationRouteDefinitionBase>[profileRoute],
);
Module не содержит индекс вкладки, родительский Shell или итоговый префикс URL.
Соберите дерево Scope
scopes: const <NavigationScopeDefinition>[
NavigationScopeDefinition(id: rootScopeId),
NavigationScopeDefinition(id: shellScopeId, parentId: rootScopeId),
NavigationScopeDefinition(id: catalogScopeId, parentId: shellScopeId),
NavigationScopeDefinition(id: profileScopeId, parentId: shellScopeId),
],
mounts: <NavigationMount>[
shellMount,
NavigationMount(
id: const NavigationMountId('catalog-tab'),
moduleId: catalogModule.id,
scopeId: catalogScopeId,
pathPrefix: '/catalog',
),
NavigationMount(
id: const NavigationMountId('profile-tab'),
moduleId: profileModule.id,
scopeId: profileScopeId,
pathPrefix: '/profile',
),
],
Приложение само решает, в каком Scope и под каким URL смонтировать каждый Module. Тот же Catalog Module можно использовать в другом приложении с другим префиксом.
Инициализируйте стеки один раз
В didChangeDependencies() один раз вызовите setStack для каждой вкладки, затем активируйте Catalog и Shell. Не выполняйте это в каждом build().
await navigation.setStack(
const <NavigationTarget<Object?>>[
NavigationTarget<Object?>(
destination: CatalogDestination(),
mountId: NavigationMountId('catalog-tab'),
scopeId: catalogScopeId,
),
],
scopeId: catalogScopeId,
);
await navigation.setStack(
const <NavigationTarget<Object?>>[
NavigationTarget<Object?>(
destination: ProfileDestination(),
mountId: NavigationMountId('profile-tab'),
scopeId: profileScopeId,
),
],
scopeId: profileScopeId,
);
await navigation.activateBranch(catalogScopeId);
await navigation.activateBranch(shellScopeId);
Свяжите NavigationBar с ветвью
return Scaffold(
body: const NavigationActiveOutlet(parentScopeId: shellScopeId),
bottomNavigationBar: NavigationBar(
selectedIndex: active == profileScopeId ? 1 : 0,
onDestinationSelected: (index) {
context.readNavigation.activateBranch(
index == 0 ? catalogScopeId : profileScopeId,
);
},
destinations: const <NavigationDestination>[
NavigationDestination(icon: Icon(Icons.store), label: 'Catalog'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
NavigationActiveOutlet показывает Navigator выбранной дочерней ветви.
Проверьте сохранение стеков
Откройте Product с scopeId: catalogScopeId, переключитесь на Profile и вернитесь. При обычном переключении не вызывайте resetBranch: эта операция намеренно уничтожает состояние ветви.
await context.readNavigation.resetBranch(profileScopeId);
Такой сброс уместен, например, после выхода из аккаунта, когда приватная история Profile больше не должна сохраняться.