Flutter web - How to listen on open Drawer state and close it

1,037

Solution 1

Here is the Solution.

You're Code is enough. Just a few changes to your code

  1. Wrap this Scaffold.of(context).openEndDrawer(); in

    WidgetsBinding.instance.addPostFrameCallback((_) {
      Scaffold.of(context).openEndDrawer(); //No Error
        ///The Error was coming, As you're trying to build a widget when it is 
        ///rebuilding widget Tree due to the change in the width of the browser.
        ///Wrapping it inside ensures that the code will run after the build.
    });
    
  2. Don't Use setState(() {});

  3. Use 520 instead of 500


Reason

The error was coming, As you're trying to build a widget when it is rebuilding Widget Tree due to the change in the width of the browser. Wrapping this Scaffold.of(context).openEndDrawer(); inside WidgetsBinding.instance.addPostFrameCallback((_) {}); ensures that the code will run after the widget get's build.

Here's Updated Code

  class HomePage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      final size = MediaQuery.of(context).size.width;
      print(size);
      return Scaffold(
        drawer: Drawer(),
        body: CustomNavBar(
          screenSize: size,
        ),
      );
    }
  }
  
  class CustomNavBar extends StatefulWidget {
    final double screenSize;
  
    const CustomNavBar({
      Key key,
      this.screenSize,
    }) : super(key: key);
  
    @override
    _CustomNavBarState createState() => _CustomNavBarState();
  }
  
  class _CustomNavBarState extends State<CustomNavBar> {
    @override
    Widget build(BuildContext context) {
      if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 520) {
        print("Drawer is Opened");
        WidgetsBinding.instance.addPostFrameCallback((_) {
          Scaffold.of(context).openEndDrawer(); //No Error
          ///The error was coming, As you're trying to build a widget when it is 
          ///rebuilding widget Tree due to the change in the width of the browser.
          ///Wrapping it inside ensure that the code will run after the build.
        });
        // Don't call setState((){}); Not Required;
        // as every time you change the width it rebuilds all the widget again
        // setState(() {});
      }
  
      return widget.screenSize > 520
          ? Container(color: Colors.red) //desktop screen
          : Center(
              //mobile screen
              child: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () => Scaffold.of(context).openDrawer(),
              ),
            );
    }
  }

The error was

  The following assertion was thrown while notifying status listeners for 
  AnimationController:
  setState() or markNeedsBuild() called during build.
  This Scaffold widget cannot be marked as needing to build because the framework is 
  already in the
  process of building widgets.  A widget can be marked as needing to be built during 
  the build phase
  only if one of its ancestors is currently building. This exception is allowed 
  because the framework
  builds parent widgets before children, which means a dirty descendant will always be 
  built.
  Otherwise, the framework might not visit this widget during this build phase.
  The widget on which setState() or markNeedsBuild() was called was:
  Scaffold
  The widget which was currently being built when the offending call was made was:
  CustomNavBar

Tip

Use the Layout Builder for Responsive UI.

@override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 800) {
          return XScreen();
        } else if (constraints.maxWidth < 1200 && constraints.maxWidth > 800) {
          return Yscreen()?? ZScreen();
        } else {
          return XScreen()?? ZScreeen();
        }
      },
    );
  }

Solution 2

You shouldn't have to close the drawer manually. Why not just get rid of the drawer when the screen width is less than 500?

class SampleDrawer extends StatelessWidget {
  final GlobalKey<ScaffoldState> k = GlobalKey();

  @override
  Widget build(BuildContext context) {
    // new
    final size = MediaQuery.of(context).size.width;
    if (k.currentState.isDrawerOpen && size < 500) {
      Navigator.pop(context); // close drawer
    }

    return Scaffold(
      key: k,
      drawer: size > 500 ? Drawer() : null,
      body: CustomNavBar(),
    );
  }
}

class CustomNavBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size.width;

    return size > 500
        ? Container(color: Colors.red) //desktop screen
        : Center( //mobile screen
            child: IconButton(
              icon: Icon(Icons.menu),
              onPressed: () => Scaffold.of(context).openDrawer(),
            ),
          );
  }
}

The Scaffold will be rebuilt whenever the width of the device changes, and the drawer will automatically be omitted if the width is less than 500.

Share:
1,037
Raine Dale Holgado
Author by

Raine Dale Holgado

BS in Computer Engineering, graduated from Silliman University. Interested in Web/Mobile development and Dart/Flutter. Love to explore new technology.

Updated on December 24, 2022

Comments

  • Raine Dale Holgado
    Raine Dale Holgado over 1 year

    Im working on flutter responsive web UI. And I want to close the opened drawer on a specific screen width for mobile and desktop screen width, so if I stretch my browser, the drawer should close.

    For example I opened the drawer (screen width less than 500)

    enter image description here

    And when the screen width is greater than 500, I want the opened drawer to automatically close.

    Note: When the Drawer is opened. I have a code already that checked the screen width that show a button menu drawer or not. But basically, when the user open the drawer then suddenly stretch the browser the drawer should closed.

    enter image description here

    Code below. Thanks for the help

    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final size = MediaQuery.of(context).size.width;
    
        return Scaffold(
          drawer: Drawer(),
          body: CustomNavBar(screenSize: size),
        );
      }
    }
    
    class CustomNavBar extends StatefulWidget {
      final double screenSize;
      const CustomNavBar({Key key, this.screenSize}) : super(key: key);
    
      @override
      _CustomNavBarState createState() => _CustomNavBarState();
    }
    
    class _CustomNavBarState extends State<CustomNavBar> {
      @override
      Widget build(BuildContext context) {
        if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 500) {
          print("Drawer is Opened");
          Scaffold.of(context).openEndDrawer(); //animation error
          setState(() {});
        }
    
        return widget.screenSize > 500
            ? Container(color: Colors.red) //desktop screen
            : Center(
                //mobile screen
                child: IconButton(
                  icon: Icon(Icons.menu),
                  onPressed: () => Scaffold.of(context).openDrawer(),
                ),
              );
      }
    }
    
    
  • Raine Dale Holgado
    Raine Dale Holgado over 3 years
    tried that before but when the drawer is currently opened and making it to null it throws widget error
  • Ganesh Tiwari
    Ganesh Tiwari over 3 years
    This solution works for me on my machine you should give it a try ;)
  • Raine Dale Holgado
    Raine Dale Holgado over 3 years
    @GaneshTiwari, that only work on managing the drawer. But my question is the Opened Drawer. Try opening the drawer then suddenly stretch it, use that code again.. It will cause some animation errors.. I need help on managing the Opened Drawer.
  • Raine Dale Holgado
    Raine Dale Holgado over 3 years
    Thanks that solved the problem. just a follow up question , i can also use this WidgetsBinding.instance.addPostFrameCallback((_) {}); on initstate right?..
  • DIVYANSHU SAHU
    DIVYANSHU SAHU over 3 years
    Yes you can I usually use WidgetsBinding.instance.addPostFrameCallback((_) {}); on InitState.
  • uanirudhx
    uanirudhx over 3 years
    @Reign please try my new code. I have edited the answer.