Home > Mobile >  Flutter - navigating via a function in another class - how to use the root navigator?
Flutter - navigating via a function in another class - how to use the root navigator?

Time:03-22

I would like to handle the page navigation in a separate class. When a page is clicked it calls a function in a class that figures out the page required and navigates the user to the correct page.

The class is set as a provider in the main/root material app, called "pageChanger". I pass in the context as a pointer (cn)

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => PageChanger(
         cn: context,
       )
    ),
  ],

The main menu is a drawer, also located in main/root material app.

When a menu item is clicked it sends the index number of that menu item to "changePage" function in the "pageChanger" class:

   onTap: () {
      pageChanger.changePage(pageNum);

The "changePage()" function retrieves all the necessary info, gets the required page data (this all works fine) and then navigates to the page.

  changePage(num _pageNum) {
     //get info from list of pages, returns with a named route, eg. namedRoute = "/news"
     //..
     //....then...take user to page
       Navigator.of(cn).pushNamed(namedRoute); // <--this doesn't work :(

However this last bit doesn't work. Navigator.of(cn) gives me an error: "Another exception was thrown: Navigator operation requested with a context that does not include a Navigator."

...my question is, how do I therefore call the root navigator?

many thanks (I've simplified the below question as much as possible. There are reasons why I'm doing it this way that are not linked to the actual question:)

Note: Here is a copy of the main widget, and the function that created the list of routes.

 Widget build(BuildContext context) 
 {
   return MaterialApp(
     home: MultiProvider(
       providers: [
         ChangeNotifierProvider(
            create: (BuildContext context) => PageChanger(
                 cn: context ,
            ),
          ),
       ],
       child: Builder(
          builder: (BuildContext context) {
             return MaterialApp(
                 initialRoute: '/',
                 routes: getPages(),            
                 builder: (context, child) {
                    return Scaffold(
                      drawer: const MainMenu(),
                      body: child,
                   );
                },
             );
          }
        ),
      ),
    );
  }




  Map<String, Widget Function(BuildContext)> getPages() {

       Map<String, Widget Function(BuildContext)> pages = {};

       //cycle through all data in projectvariables.pageData (elsewhere in project)

       for (var page in projectvariables.pageData) 
       {
         var obj;

         switch (page["link"]) {
             case "/":
               obj = HomePage(
                  title: page["title"],            
               );
             break;

             case "/news":
               obj = News(
                 title: page["title"],
               );
             break;
         }
         pages[page["link"]] = (_) => obj;
       }
       return pages;
   }

CodePudding user response:

You could pass BuildContext to your changePage method to use the Navigator with that context, like so:

changePage(num _pageNum, BuildContext cn) {
     //get info from list of pages, returns with a named route, eg. namedRoute = "/news"
     //..
     //....then...take user to page
     Navigator.of(cn).pushNamed(namedRoute); // <-- use passed context (cn) here

Working example:

MaterialApp(
      routes: {
        "/news": (ctx) => News(),
      },
      home: Scaffold(
        body: Builder(
          builder: (ctx) {
            return Container(
              child: ElevatedButton(
                child: Text('Go to News Page'),
                onPressed: () {
                  // changePage(1, ctx); // related to your code
                  Navigator.of(ctx).pushNamed("/news"); // e.g. using the new context (ctx) from Builder Widget
                },
              ),
            );
          }
        ),
      ),
    )

Or in your case it should be something like this I guess:

MaterialApp(
      initialRoute: '/',
      routes: getPages(),
      home: MultiProvider(
        providers: [
          ChangeNotifierProvider(
            create: (BuildContext context) => PageChanger(
              cn: context,
            ),
          ),
        ],
        child: Builder(
          builder: (BuildContext context) {
            return Scaffold(
              drawer: const MainMenu(),
              body: child,
            );
          },
        ),
      ),
    )

CodePudding user response:

I found the answer. In this case the main menu is a shared persisten menu (see https://medium.com/@alimohammadi7117/shared-navigationdrawer-for-all-pages-in-flutter-9208b5993481), and therefore I had to pass the navigator through to the main menu as "(child!.key as GlobalKey)"

 ..main.dart build.....
 ..
 builder: (context, child) {
          return Scaffold(
            drawer: MainMenu(navigator:(child!.key as GlobalKey<NavigatorState>)),
            body: child,
          );
        },

...in the main menu I call the changePage() function but pass through this navigator name:

 ...mainmenu.dart widget item
 ..
 onTap: () {
     pageChanger.changePage(pageNum,navigator);
 }

...and in change changePage() function:

 changePage(num _pageNum, navigator) {
  pageObject = getPage(pageNum);  //my internal function to get page
  navigator.currentState?.pushReplacementNamed( pageObject(linkName) );
  • Related