flutterflutter-drawer

Body of a Scaffold is rebuilt when opening a Drawer menu


I have a Stateful widget which builds a Scaffold. I am using a drawer in the Scaffold for a side menu. Also, the body of the Scaffold is a FutureBuilder which gets data from firestore database and displays the info in a Card in the body. There seems to be an issue while opening a drawer which causes the body to be rebuilt and the future in the FutureBuilder queries the data again. This happens again again when the drawer is popped. I have other buttons in the Scaffold in both appbar and bottomNavigationBar to navigate to different routes. The body isn't being rebuilt while navigating these routes. Could anyone please help as to why is it happening with the Drawer?

Below is the code snipped.

Thanks

class CustomScaffoldState extends State<CustomScaffold> {

Widget build(BuildContext context) {

  return Scaffold(
    drawer: sideMenu(widget.username),

    body: FutureBuilder(
          future: getData(),
          builder: (context, snapshot) {
             if (snapshot.connectionState == ConnectionState.done) {
                 //return the Card with Info
               }
             if (snapshot.hasError) {
                print('Error');
                 }
             else{
                //return a CircularProgressIndicator
                 }
            }
           ));

//appbar and bottomNavigation bar also implemented
}
}

Solution

  • When the drawer or soft keyboard opened the state of the screen change and sometimes the build method reload automatically, please check this link for more information.

    The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:

    Route pop/push Screen resize, usually due to keyboard appearance or orientation change Parent widget recreated its child An InheritedWidget the widget depends on (Class.of(context) pattern) change This means that the build method should not trigger an http call or modify any state.

    How is this related to the question?

    The problem you are facing is that your build method has side-effects/is not pure, making extraneous build call troublesome.

    Instead of preventing build call, you should make your build method pure, so that it can be called anytime without impact.

    In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:

    class Example extends StatefulWidget {
      @override
      _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
      Future<int> future;
    
      @override
      void initState() {
        future = Future.value(42);
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: future,
          builder: (context, snapshot) {
            // create some layout here
          },
        );
      }
    }
    

    I know this already. I came here because I really want to optimize rebuilds

    It is also possible to make a widget capable of rebuilding without forcing its children to build too.

    When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.

    The easiest way is to use dart const constructors:

    @override
    Widget build(BuildContext context) {
      return const DecoratedBox(
        decoration: BoxDecoration(),
        child: Text("Hello World"),
      );
    }
    

    Thanks to that const keyword, the instance of DecoratedBox will stay the same even if build were called hundreds of times.

    But you can achieve the same result manually:

    @override
    Widget build(BuildContext context) {
      final subtree = MyWidget(
        child: Text("Hello World")
      );
    
      return StreamBuilder<String>(
        stream: stream,
        initialData: "Foo",
        builder: (context, snapshot) {
          return Column(
            children: <Widget>[
              Text(snapshot.data),
              subtree,
            ],
          );
        },
      );
    }
    

    In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column do. It happens because, thanks to the closure, the instance of MyWidget didn't change.

    This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.

    You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.