Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rewrite nested Navigator 2.0 example using provider

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();
  }
}

like image 484
Damian K. Bast Avatar asked Nov 06 '22 03:11

Damian K. Bast


1 Answers

  1. Add 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(),
        ),
      );
    
    }
  1. Òn your InnerRouterDelegate use .watch to capture your index change inside the build.
@override
  Widget build(BuildContext context) {
    var idx = context.watch<MyAppState>().selectedBottomNavIndex;
    return Navigator(
...
  1. On your 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.

like image 196
Roddy R Avatar answered Nov 24 '22 03:11

Roddy R