I am looking to do a rewrite of the official example for writing a nested navigator with bottom navigation. Specifically, I want to write another implementation using provider for lifting up state, instead of passing a callback down to the page like it is happening here with ˋhandleBookTappedˋ.
Q: How to use Provider to pass down BookAppState to the pages, while properly updating both outer and inner navigators without loosing state of the tabs.
class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
BooksAppState get appState => _appState;
BooksAppState _appState;
set appState(BooksAppState value) {
if (value == _appState) {
return;
}
_appState = value;
notifyListeners();
}
InnerRouterDelegate(this._appState);
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
if (appState.selectedIndex == 0) ...[
FadeAnimationPage(
child: BooksListScreen(
books: appState.books,
onTapped: _handleBookTapped,
),
key: ValueKey('BooksListPage'),
),
if (appState.selectedBook != null)
MaterialPage(
key: ValueKey(appState.selectedBook),
child: BookDetailsScreen(book: appState.selectedBook),
),
] else
FadeAnimationPage(
child: SettingsScreen(),
key: ValueKey('SettingsPage'),
),
],
onPopPage: (route, result) {
appState.selectedBook = null;
notifyListeners();
return route.didPop(result);
},
);
}
@override
Future<void> setNewRoutePath(BookRoutePath path) async {
// This is not required for inner router delegate because it does not
// parse route
assert(false);
}
void _handleBookTapped(Book book) {
appState.selectedBook = book;
notifyListeners();
}
}
MultiProvider
on your App (as your probably will use more than one). Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MyAppState()),
//ChangeNotifierProvider(create: (_) => Counter()),
],
child: RootRouter(),
),
);
}
InnerRouterDelegate
use .watch
to capture your index change inside the build.@override
Widget build(BuildContext context) {
var idx = context.watch<MyAppState>().selectedBottomNavIndex;
return Navigator(
...
bottomNavigationBar
replace appState.
by context.watch<MyAppState>().
(note that I am using enum for index, slightly different from the official example)bottomNavigationBar:
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: BottomNavigationBarItems.items(),
currentIndex: context.watch<MyAppState>().selectedBottomNavIndex.index,
//currentIndex: appState.selectedBottomNavIndex.index, //replace
onTap: (newIndex) {
context.read<MyAppState>().selectedBottomNavIndex= NavItem.values[newIndex];
//appState.selectedBottomNavIndex = NavItem.values[newIndex]; //replace
},
),
This will make it work with Mobile. There is a lot of web stuff that needs to be updated too, and code cleaned/removed. appState
variable becomes obsolete.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With