6

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?

4 Answers4

4

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.

Calvin Gonsalves
  • 1,738
  • 11
  • 15
  • While the solution proposed by [Randal Schwartz](https://stackoverflow.com/a/65726864/6242209) works, I ended up doing it this way since it was a bit simpler to integrate it in my existing application. Thank you! – Benjamin Smrdelj Jan 15 '21 at 10:39
3

LayoutBuilder seems preferable over every use of MediaQuery for sizing a viewport (either the whole screen, or the space left in a column or other layout).

LayoutBuilder also works hard to avoid rebuilding its child if the size doesn't change and the parents haven't had to re-layout.

The builder function is called in the following situations:

  • The first time the widget is laid out.
  • When the parent widget passes different layout constraints.
  • When the parent widget updates this widget.
  • When the dependencies that the builder function subscribes to change.

The builder function is not called during layout if the parent passes the same constraints repeatedly.

And you don't have to think about "the height of the appbar" ever again, because you're getting the space left, not the total space on the screen.

Check it out: https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html

Randal Schwartz
  • 39,428
  • 4
  • 43
  • 70
0

In my case, the problem was happening because I was controlling the focus manually using:

onEditingComplete: () {
   FocusScope.of(context).nextFocus();
}

The context used was the Parent's context, and it was causing the rebuilding. Not sure why it happened, but it stopped once I've wrap the TextFormField with a Builder and started using its context instead.

Note: I'm also using MediaQuery.of(context).size.height normally (without the rebuild side effect) to set the Widget's Parent height

Gui Silva
  • 1,341
  • 15
  • 18
-1

Instead of:

MediaQuery.of(context).size..

Try to use:

MediaQuery.sizeOf(context)...

no unnecessary rebuilds

Alexey Lo
  • 359
  • 3
  • 8