How to use MediaQuery in flutter

As the name suggests, MediaQuery widget carries information about a piece of media. You’ve guessed it right, here media means window, with its size or orientation. MediaQuery also plays an important role in obtaining or controlling the size of the current window of the device. Either it could be a browser, in case of Flutter web application. Or it could be a size of the mobile device where our Flutter application is running. In this section we’ll learn how we can use MediaQuery.of(context) function.

For example, the MediaQuery has a property called size. The size of the MediaQuery contains the width and the height of the current window.

For a given context, MediaQuery.of(context) function comes to our help. We’ll see how it works in a minute.

With reference to this, if no MediaQuery is in scope, then the MediaQuery.of(context) function will throw an exception. However, we can avoid that with another function, MediaQuery.maybeOf(context).

When our Flutter application queries the current media using the MediaQuery.of function, it causes the widget to rebuild. It happens because if the user rotates the device, the MediaQueryData changes.

Another important feature of MediaQuery is, it’s always there. We can simply access in the build method by calling MediaQuery.of function.

Not only we get the information about the size, but also about the orientation, which decides whether the user wants to view the Flutter application in the portrait mode or in the landscape mode.

Firstly, let’s see how we can adjust the size of the AppBar of our Flutter app, with the help of MediaQuery.of(context) function.

Secondly, we’ll take a look at the orientation property.

What is MediaQuery in flutter?

There are two main modes in Flutter. They are portrait mode and landscape mode. As a result, when we change the mode, the look also changes.

Let’s take a look at the screenshots that follow one another.

MediaQuery adjusting width of AppBar and Container
MediaQuery adjusting width of AppBar and Container

The above screenshot shows us how the screen size of AppBar and the body of the child widget varies.

Now, as we run the app again and want to view it in full screen, the width of the screen size automatically adjusts itself.

MediaQuery adjusting width of AppBar and Container in landscape mode
MediaQuery adjusting width of AppBar and Container in landscape mode

Actually, the MediaQuery establishes a Widget sub-tree in which media queries resolve to the given data.

For example, we can read the MediaQueryData.size property from the MediaQueryData returned by MediaQuery.of function and adjusts our Flutter app’s size accordingly.

Now, the time has come to take a look at the code snippet.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/global_theme.dart';

void main() {
  runApp(
    /// Providers are above [Root App] instead of inside it, so that tests
    /// can use [Root App] while mocking the providers
    MultiProvider(
      providers: [
        Provider<GlobalTheme>(
          create: (context) => GlobalTheme(),
        )
      ],
      child: const MediaQueryHome(),
    ),
  );
}

class MediaQueryHome extends StatelessWidget {
  const MediaQueryHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final ThemeData globalTheme = Provider.of<GlobalTheme>(context).globalTheme;
    return MaterialApp(
      title: 'MediaQuery Sample',
      debugShowCheckedModeBanner: false,
      theme: globalTheme,
      home: const MediaQuerySample(),
    );
  }
}

class MediaQuerySample extends StatelessWidget {
  const MediaQuerySample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //var screenSize = MediaQuery.maybeOf(context).size;
    var screenSize = MediaQuery.maybeOf(context)!.size;

    return Scaffold(
      appBar: appBarSize(screenSize),
      body: Container(
        margin: const EdgeInsets.all(5),
        padding: const EdgeInsets.all(5),
        width: screenSize.width,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(16.0),
          color: Colors.yellowAccent,
        ),
        child: Text(
          'MediaQuery Sample',
          style: Theme.of(context).textTheme.headline1,
        ),
      ),
    );
  }

  PreferredSize appBarSize(Size screenSize) {
    return PreferredSize(
      preferredSize: Size(screenSize.width, 1000),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Row(
          children: [
            const Text(
              'Portrait',
              style: TextStyle(color: Colors.blue),
            ),
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  InkWell(
                    onTap: () {},
                    child: const Text(
                      'Landscape',
                      style: TextStyle(color: Colors.deepOrange),
                    ),
                  ),
                  SizedBox(width: screenSize.width / 20),
                ],
              ),
            ),
            InkWell(
              onTap: () {},
              child: const Text(
                'Sign Up',
                style: TextStyle(color: Colors.deepPurple),
              ),
            ),
            SizedBox(
              width: screenSize.width / 50,
            ),
            InkWell(
              onTap: () {},
              child: const Text(
                'Login',
                style: TextStyle(color: Colors.green),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

The above code is not a very complex one. We’ve managed the theme mostly by a custom global ThemeData class that resides in the model folder. The Provider package has provided the value.

For the full code please visit the respective GitHub Repository.

Why we use media query in flutter?

We know that making a flutter app responsive is no easy task. However, it’s not a very difficult task too.

Although beginners might confuse responsive with adaptive, still we need to understand both the terms first.

Responsive does not mean adaptive. There is a difference.

We’ve just seen how MediaQuery tackles the responsiveness. It adjusts width of the device accordingly.

The advantage of Flutter app lies in its flexibility. Above all, it has a one codebase project that can cater to Android and iOS both. Subsequently, one single widget tree checks the nature of the device.

If it were Android it would adapt android-way. It takes the Material design look and styles. However, for iOS, the flutter app searches for Cupertino look and styles.

So the term Adaptive refers to these activities.

On the other hand, the term Responsive mainly deals with the mode of the device. It wants to know the user’s preference. Does she want to view it in portrait mode or landscape mode.

Let’s take a look at another important property of MediaQuery.

Orientation.

We usually display the screen on the portrait mode; however, in the landscape mode we might display something other.

Let us take a loo at the screenshots first to get an idea.

Firstly, the portrait size.

MediaQuery adjusting orientation of Flutter App
MediaQuery adjusting orientation of Flutter App

Secondly, in the landscape mode we see something completely different.

MediaQuery changes orientation of Flutter App in landscape mode
MediaQuery changes orientation of Flutter App in landscape mode

Although the same flutter app, yet we get two separate views as the orientation changes.

Here is the complete code, that will answer how we can make it possible.

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MediaQueryHome(),
  );
}

class MediaQueryHome extends StatelessWidget {
  const MediaQueryHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MediaQuery Sample',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MediaQuerySample(),
    );
  }
}

class MediaQuerySample extends StatelessWidget {
  const MediaQuerySample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final isPortrait =
        MediaQuery.of(context).orientation == Orientation.portrait;
    final appBar = AppBar(
      title: const Text(
        'Media Query example',
      ),
      actions: <Widget>[
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () {},
        ),
      ],
    );

    return Scaffold(
      appBar: AppBar(
        title: appBar,
      ),
      body: SingleChildScrollView(
        child:
            isPortrait ? PortraitSize(appBar: appBar) : const LandscapeSize(),
      ),
    );
  }
}

The first half of the code shows clearly that portrait size refers to a separate widget. If that’s not in the portrait mode, it automatically refers to the landscape mode which is another widget.

And everything is decided by these two lines:

final isPortrait =
        MediaQuery.of(context).orientation == Orientation.portrait;
...
 isPortrait ? PortraitSize(appBar: appBar) : const LandscapeSize(),

Now, we can check the two separate widgets separately.

Firstly, take a look at the portrait size widget.

class PortraitSize extends StatelessWidget {
  const PortraitSize({
    Key? key,
    required this.appBar,
  }) : super(key: key);

  final AppBar appBar;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        SizedBox(
          height: (MediaQuery.of(context).size.height -
                  appBar.preferredSize.height -
                  MediaQuery.of(context).padding.top) *
              0.5,
          child: ListView(
            children: [
              Container(
                margin: const EdgeInsets.all(8),
                padding: const EdgeInsets.all(15),
                alignment: Alignment.topCenter,
                decoration: BoxDecoration(
                  color: Colors.purple,
                  border: Border.all(
                    width: 5,
                    color: Colors.grey,
                  ),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Card(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Books'),
                            subtitle: Text('25.00'),
                          ),
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Groceries'),
                            subtitle: Text('250.00'),
                          ),
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Fruits'),
                            subtitle: Text('480.00'),
                          ),
                          Wrap(
                            alignment: WrapAlignment.spaceBetween,
                            direction: Axis.horizontal,
                            children: <Widget>[
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' ADD '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' UPDATE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' DELETE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' NEXT PAGE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
        SizedBox(
          height: (MediaQuery.of(context).size.height -
                  appBar.preferredSize.height -
                  MediaQuery.of(context).padding.top) *
              0.5,
          child: ListView(
            children: [
              Container(
                margin: const EdgeInsets.all(8),
                padding: const EdgeInsets.all(15),
                alignment: Alignment.topCenter,
                decoration: BoxDecoration(
                  color: Colors.purple,
                  border: Border.all(
                    width: 5,
                    color: Colors.grey,
                  ),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Card(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Books'),
                            subtitle: Text('25.00'),
                          ),
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Groceries'),
                            subtitle: Text('250.00'),
                          ),
                          const ListTile(
                            leading: Icon(Icons.album),
                            title: Text('Fruits'),
                            subtitle: Text('480.00'),
                          ),
                          Wrap(
                            alignment: WrapAlignment.spaceBetween,
                            direction: Axis.horizontal,
                            children: <Widget>[
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' ADD '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' UPDATE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' DELETE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                              Container(
                                margin: const EdgeInsets.all(8),
                                child: ElevatedButton(
                                  onPressed: () => {},
                                  child: const Text(' NEXT PAGE '),
                                  style: ElevatedButton.styleFrom(
                                    primary: Colors.purple,
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 20, vertical: 20),
                                    textStyle: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

It’s quite long because, we have decorated the portrait mode with many widgets.

However, the landscape mode is quite simple.

class LandscapeSize extends StatelessWidget {
  const LandscapeSize({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(8),
      child: const Text(
        'Landscape Mode',
        style: TextStyle(
          fontFamily: 'Allison',
          fontSize: 200,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

To sum up, with a few lines of code, we can adjust the width and orientation of Flutter application, thanks to MediaQuery.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

One response to “How to use MediaQuery in flutter”

  1. […] know how it works. Secondly, we’ll learn how we can use Stack widget to build and design an adaptive and responsive Flutter application that runs on web and mobile platform at the same […]

Leave a Reply