Search code examples
flutterflutter-widgetflutter-build

Flutter - prevent rebuild when using MediaQuery


I want to use MediaQuery to build widgets based on screen height and width. The problem, also referenced in #26004, is that I only want to query the size data once, for example in initState. MediaQuery documentation states

Querying the current media using MediaQuery.of will cause your widget to rebuild automatically whenever the MediaQueryData changes (e.g., if the user rotates their device).

, but that causes unnecessary rebuilds in my application. Specifically, it causes rebuild of widgets if there are changes to insets or padding (such as when keyboard is displayed).

Is there an alternative to MediaQuery which wouldn't cause rebuilds when MediaQueryData changes?


Solution

  • I had this issue as well and initially thought that the MediaQuery is causing unnecessary rebuilds, but if you think about it you do want the widgets to rebuild (in cases of device rotation, keyboard popup) for the app to have a responsive design.

    You could do something like this:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: Builder(builder: (context) {
            ResponsiveApp.setMq(context);
            return MyHomePage(title: 'Flutter Demo Home Page');
          }),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Flex(
              direction:
                  ResponsiveApp().mq.size.width > ResponsiveApp().mq.size.height
                      ? Axis.horizontal
                      : Axis.vertical,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    class ResponsiveApp {
      static MediaQueryData _mediaQueryData;
    
      MediaQueryData get mq => _mediaQueryData;
    
      static void setMq(BuildContext context) {
        _mediaQueryData = MediaQuery.of(context);
      }
    }
    
    

    I set the mediaQueryData at the beginning with ResponsiveApp.setMq(context) and I used the Builder because you can only use the MediaQuery one context below the MaterialApp widget. After the _mediaQueryData is set you can get it whenever you want to build widgets based on the screen size.

    In this code I just change the Axis direction when the device is rotated and the widget needs to rebuild to show the changed direction.

    You could also have something like :

    
        if (_mediaQueryData.size.shortestSide < 400)
             //phone layout
        else if(_mediaQueryData.size.shortestSide >= 400 && _mediaQueryData.size.shortestSide < 600)
              //tablet layout
        else
              //web layout
    
    

    and resizing the window in web will cause the widgets to rebuild multiple times and display the desired layout.

    But if you don't want to use MediaQuery at all, you can check the Window class from dart:ui.