0

I want to persist Navigation Drawer across all screens. There have been questions on stack over this but my question is little different fo e.g. Persisting AppBar Drawer across all Pages Flutter

I have Navigation Drawer with list of items called A,B and C. On Clicking of A in Navigation Drawer,Screen A opens and respectively same thing for B and C. Now the C screen has a button and on click of that button I am going to screen D, now though the screen D shows the Navigation Drawer icon, the drawer never opens up. I tried printing a statement in the method where drawer is called and the print statement does prints but the drawer never opens. Following is my code

I have a base class whose drawer is as follows

 class BaseScreen extends StatefulWidget {
  final List<Menu> menuList;
  final String userType;
  final String userId;

  const BaseScreen({Key key, this.menuList, this.userType, this.userId})
      : super(key: key);

  @override
  BaseScreenState createState() {
    return new BaseScreenState();
  }
}

class BaseScreenState extends State<BaseScreen> {
  String screenNameSelected = "A";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          _getDrawerItemWidget(screenNameSelected),

        ],
      ),
      drawer: SizedBox(
        width: 100,
        child: Drawer(
          child: ListView.separated(
            separatorBuilder: (context, index) => Material(
                  elevation: 2,
                  shadowColor: shadow,
                  child: Divider(
                    color: white,
                  ),
                ),
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                onTap: () {
                  openScreen(
                      widget.menuList[index].title,
                      widget.userId,
                      MenuList.returnLoginType(widget.userType).toString(),
                      context);
                  Navigator.pop(context);
                },
                title: Padding(
                  padding: const EdgeInsets.only(top: 8),
                  child: Image.asset(
                    widget.menuList[index].image,
                    width: 35,
                    height: 35,
                  ),
                ),

              );
            },
            itemCount: widget.menuList?.length ?? 0,
            shrinkWrap: true,
            physics: BouncingScrollPhysics(),
          ),
        ),
      ),
    );
  }

  _getDrawerItemWidget(String selectedScreenName) {
    switch (selectedScreenName) {
      case A:
        return A();
      case B:
        return B();
      case C:
        return C();

      default:
        return Container();
    }
  }

  void openScreen(String screenName, String userId, String loginType,
      BuildContext context) {
    if (screenName.toLowerCase() == "A".toLowerCase()) {
      setState(() {
        screenNameSelected = "A";
      });
    } else if (screenName.toLowerCase() == "B".toLowerCase()) {
      setState(() {
        screenNameSelected = "B";
      });
    } else if (screenName.toLowerCase() == "C".toLowerCase()) {
      setState(() {
        screenNameSelected = "C";
      });
    }

  }

}

I have created a custom class for App bar as my app bar is highly customized

class CustomAppBar extends StatelessWidget {
  final String subTitleText;
  final String subTitleImage;

  const CustomAppBar(
      {Key key, @required this.subTitleText, @required this.subTitleImage})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        SafeArea(
          child: Column(
            children: <Widget>[
              Builder(builder: (context) => customAppBar(context)),
              SizedBox(
                height: 5.0,
              ),
              subTitleRow(
                subTitleText,
                subTitleImage,
              )
            ],
          ),
        ),

      ],
    );
  }
}

Widget subTitleRow(String subtitleText, String subtitleImage) {
.........
}


Widget customAppBar(BuildContext context) {
  return Material(
    elevation: 5.0,
    shadowColor: shadow,
    child: SafeArea(
      child: Stack(
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              Image.asset(
                "images/toolbar_logo.webp",
                width: 80,
                height: 50,
              ),
            ],
          ),
          IconButton(
            icon: Image.asset("images/menu.webp"),
            onPressed: () {
              Scaffold.of(context).openDrawer();
            },
            iconSize: 20,
          ),
        ],
      ),
    ),
  );
}

Now my class A,B and C does not have a scaffold but I had provided my class D with a scaffold and I think that is causing the problem. Not providing class D with a Scaffold does not give a proper layout

Code for Class A,B and C is as follows

class A extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ShortBioProvider(
      child:  Column(
            children: <Widget>[
              CustomAppBar(
                subTitleImage: "images/settings.webp",
                subTitleText: SETTINGS.toUpperCase(),
              ),
              SizedBox(
                height: 20,
              ),
              SettingsList(),
            ],
          ),

    );
  }
}

B and C are very similar to A.

Now for class D code

class D extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Column(children: <Widget>[
      CustomAppBar(
        subTitleImage: "images/settings.webp",
        subTitleText: D.toUpperCase(),
      ),
      SizedBox(
        height: 20,
      ),
      ........
    ],),);
  }
}

I have edited the above classes a lot so there may be a missing bracket or semicolon here or there

  • It looks like you're building widgets within the same class using functions instead of their own classes. That can cause problems to do with BuildContext, but it's hard to tell without all of your code. I'd first recommend moving things that are basically an enclosed widget (i.e. the app bar) into their own Stateful or Stateless widget classes, and then if that doesn't help posting all of the relevant code (i.e. screen D in particular might be interesting). If there's proprietary things in there (and even if there isn't) it would be helpful if you strip out anything irrelevant from the code. – rmtmckenzie Feb 05 '19 at 21:22
  • @rmtmckenzie I am using class for A,B,C and D Screen. The Custom App bar is also in separate class. I will post code for class D soon –  Feb 06 '19 at 03:34
  • @rmtmckenzie Please have a look I have added code for all the classes –  Feb 06 '19 at 03:49

1 Answers1

0

Ah, now you've added your code it makes sense.

What's happening is that in D's case you have two different scaffolds:

BaseScreenState
--> Scaffold 1
    -->  drawer
         body
           --> stack
             --> D
               --> Scaffold 2
                 -->CustomAppBar ....

When you do Scaffold.of(context), it's looking up through the tree to find the closest scaffold, which in this case happens to be Scaffold 2, which doesn't have a drawer. The scaffold in your D class doesn't seem to be doing anything so removing it should fix your issue.

HOWEVER

There are some more serious problems in your code. You should almost never be using a stack to switch between pages. For one, flutter isn't able to cache widgets as well that way, but also it eliminates things like screen transitions etc.

What you should be doing instead is making a page that includes a scaffold, drawer, app bar, etc for each and every page in your app, and then switching between those using the navigator. (Note that just because you make a widget for each page, it doesn't mean that the components can't be shared).

An architecture that makes sense in flutter is as follows:

  • A top-level Stateful widget (stateful works better for hot reload, a stateless one can cause issues), something like MyApp (but change My to the name of the actual app).
  • in the build function, this makes a MaterialApp. You could potentially put the logic for building the various pages into the materialApp's onGenerateRoute - I find this can make it cleaner and easier to tell what's going on than building things on the fly.
  • if you are transitioning between pages by name (string), you should make the names into global constants.
  • A Stateful or Stateless widget for each page (unless the pages are similar enough to be differentiated with a few simple parameters), which in the build function will make a scaffold, app bar, navigational drawer, etc
  • optionally the scaffold could be its own widget as well
  • A widget for your app bar (i.e. CustomAppBar)
  • A widget for your navigation drawer

A few other things to note are:

  1. Splitting things up by build function rather than class seems like it might be faster, but it isn't. Having two many widgets within one build function (and don't be fooled, calling a function that returns a widget from the build function is the same as putting the code directly in the function for the intent of performance) can actually mean more calculations for flutter as each time that widget needs to be rebuild the entire set of classes needs to be build and checked, whereas if you use proper encapsulation in widgets then a lot of the work can be skipped if nothing changes in that part of the widget tree.
  2. It might seem like building a Scaffold, AppBar, and Drawer in each page is wasteful. However, because of how flutter caches things and intelligently scans through the widget tree, it's actually quite performant and that's how the flutter team recommends doing things.
  3. Always figure out which build context you're calling things from, then you can follow up the build tree to figure out which widget is being actually called when you call .of(context).

I hope that helps!

rmtmckenzie
  • 37,718
  • 9
  • 112
  • 99