Sitemap
Press enter or click to view image in full size
Featured Image: Creating Responsive Layouts Part I: Exploring Media Query in Flutter

Creating Responsive Layouts Part I: Exploring Media Query in Flutter

13 min readSep 3, 2024

--

A Deep Dive into MediaQuery: Creating Responsive Layouts in Flutter

Note: This blog assumes that you are using vscode, AndroidStudio or IntelliJ as IDE.

In this medium blog we will discuss about Media Query class and find how it helps in creating Responsive Layouts in Flutter.

  1. What is Media Query?
  2. How to use MediaQuery?
  3. Exploring the MediaQuery class.
  4. Creating Responsive layouts using Media Query.
  5. Best Practices while using MediaQuery.
  6. Conclusion

So, without a further ado lets begin.

https://tenor.com/uamW.gif

1. What is Media Query?

In Flutter, MediaQuery is a widget that provides information about the device’s screen and user preferences. It allows developers to build responsive and adaptive user interfaces by giving access to various details about the screen’s size, orientation, pixel density, and more. How, you ask?

Just use,

//Use this anywere in the Widget tree where BuildContext context is available
MediaQuery.of(context);

And you will now have access to everything MediaQuery has to offer.

But how MediaQuery acthually does this?

MediaQuery achives this using BuildContext which provides a high-level overview of the current state of the device screen by creating an instance of something called as MediaQueryData. It is MediaQueryData which gives access to screen’s size, orientation, pixel density, and more.

By utilizing MediaQuery developers can create layouts that can morph to fit in device screen based on device width, device height, orientation, brightness, etc. and create consistent layout on devices of various screen sizes and Platforms(Android, IOS, Windows, Linux and Web).

2. How to use MediaQuery?

As discussed above its quite easy to use MediaQuery, just use the of() method and you are good to go, the only requirement is BuildContext. MediaQuery can be used anywere in the widget tree where the BuildContext is available.

Next, in order to access the data provided by MediaQuery.of(context) you can create a variable of type MediaQueryData, let’s say mediaQuery. Now once we have instance of MediaQueryData, just use “ mediaQuery. ” and you will be able to see everything MediaQueryData has to offer.(Assuming you are using vscode, Android Studio or IntelliJ IDE)

Putting all things together, this is how you use MediaQuery.

//NOTE: only use MediaQuery where BuildContext context is available

//EITHER you can specify the type for mediaQuery as MediaQueryData
final MediaQueryData mediaQuery = MediaQuery.of(context);

//OR eleminate the type and let dart infer it
final mediaQuery = MediaQuery.of(context);

//Voilla, now you have access to everything MediaQuery has to offer.

All of that is right but what is the deal with BuildContext and it being “available” in order to use MediaQuery?

Look, it’s simple and nothing to be worried about. Here is the thing, every widget in flutter application(Stateless, Stateful or Inherited Widget) has a build() method which is used to build a widget and is a necessary override while creating any widget. This build method takes a required argument which is (drumroll…), BuildContext.

Now, since BuildContext is available to all the widgets inside the build() method and we can use the MediaQuery.of() method and pass the BuildContext to it.

We will typically write something like this in order to create a Widget and use MediaQuery.of(context) inside the build(BuildContext context) method where theBuildContext is available.

//Example Usage 1
//MediaQuery being used inside a widget

class MyCustomWidget extends StatelessWidget {
const MyCustomWidget({super.key});
@override
Widget build(BuildContext context) {
//this MediaQueryData instance can be only used
//inside the build method where the context is available
final MediaQueryData mediaQuery = MediaQuery.of(context);

return Scaffold(
body: Center(
child: Container(
width: mediaQuery.size.width * 0.5, //50% of screen width
height: mediaQuery.size.height * 0.5, //50% of screen height
color: Colors.blue,
),
),
);
}
}

Another question: Can we use MediaQuery outside widgets?

The short answer is YES. But there are things we must know.

Remember the only requirement for using MediaQuery.of(context) is BuildContext. This means that one can define a function and use BuildContext as its parameter. Then MediaQuery can be used inside the function block.

This approach is usually used to create reusable code blocks or to create a separate function to manage a growing widget tree ensuring code maintainability.

Note: Such a function should be only called where build context is available, preferably inside the widget’s build() method.

Check the below example.

//Example Usage 2
//MediaQuery being used inside a function

class MyCustomWidget extends StatelessWidget {
const MyCustomWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: customContainer(context), //call customContainer and pass context
),
);
}
}

//A funtion using MediaQuery

Container customContainer(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Container(
width: mediaQuery.size.width * 0.5, //50% of screen width
height: mediaQuery.size.height * 0.5, //50% of screen height
color: Colors.blue,
);
}

Both the approaches will produce the same output i.e, a blue Container occupying 50% of screen width and 50% of screen height .

That all we need to know about using MediaQuery in flutter.

Now let’s move to some important aspects of MediaQuery and see what MediaQuery offers and how we can utilise it.

3. Exploring the MediaQuery class.

Now, we have covered all the basics of MediaQuery and we are ready to start exploring the MediaQuery or should I say MediaQueryData class. Remember, as discussed in previous sections it is MediaQueryData that provides information about device screen and user preferences.

Media Query has multiple data members(properties) that contain information about device specific information like devicePixelRatio, orientation, padding, size, etc. Let’s go through all such important data members.

https://tenor.com/bE4um.gif

Note: I will be covering the important and most used data members and not all of them. Consider reading the docs for a detailed info

A. Data members containing information about “Device Screen”

Get Bhavesh Mankar’s stories in your inbox

Join Medium for free to get updates from this writer.

1. devicePixelRatio: Device Pixel Ratio or DPR refers to the ratio between the physical pixels on screen and logical pixels. Physical pixels are the acthual pixels present on the display of the device, whereas one logical pixel corrosponds to ‘n’ number of physical pixels. We can say something like 1 logical pixel corrospond to 3 physical pixels. If DPR for a device is 3 then, 3 physical pixels will be used to draw a line which is 1px wide. Logical Pixels are same across various devices and are used to maintain “visual consistancy”. DPR is mostly used in dealing with images with different resolutions to maintain consistancy. This is how to use devicePixelRatio

//devicePixelRatio is useful when dealing with images heiping
//in pixel perfect rendering
final double dpr = MediaQuery.of(context).devicePixelRatio;

2. padding: Padding in context of MediaQuery refers to the amount of space reserved by operating system like the system status bar(the one were we see the time and battery percentage, etc.) and the bottom region which is used for naviagtion and other puropses which may vary from one platform to other. Padding is of type EdgeInsets and has four offsets, namely botton, top, right and left. Bottom padding will have some value if the keyboard is not visible, if keyboard is Up then bottom padding will be reduced to “0”. This is how to use padding

//padding helps in determining the position and amount of pixels reserved by 
//the operating system for its own puropses
final EdgeInsets padding = MediaQuery.of(context).padding;
debugPrint("Padding => Bottom: ${padding.bottom}, Top: ${padding.top}, Left: ${padding.left}, Right: ${padding.right}");

3. orientation: Orientation is as simple as it gets. It refers to the alignment and positioning of the device. There are two types of orientations, Landscape and Potrait. In Potrait orienatation the width of device is smaller than the height and in Landscape orientation it just gets reversed, now the width is larger and height is smaller. Orientation plays a crucial role in determining the layout of the application, as changing the orientation affects the amount of information or data that can be displayed on the screen. A good application is one which accounts for different orientations. This is how to use orientation

//orientation helps in determining the position and alignment of device
final Orientation orientation= MediaQuery.of(context).orientation;
if(orientation == Orientation.landscape){
debugPrint("Device is in Landscape mode");
}else{
debugPrint("Device is in Potrait mode");
}

4. size: Size is the most used data member offered by the MediaQueryData class and provides a lot of information about the device screen. But we will be talking about three, namely width, height and aspectRatio. As the name suggests width gives the device width, height gives the device height and aspectRatio gives the ratio of device width to device height. This is how to use size

//size property helps in getting device screen dimensions
final Size size = MediaQuery.of(context).size;
debugPrint("Size => Width: ${size.width}, Height: ${size.width}, AspectRatio: ${size.aspectRatio}");

5. viewInsets : ViewInsets is of type EdgeInsets. viewInsets is used to determine the amount of space consumed by the keyboard when it is visible. If the keyboard is not visible the all the four offsets of viewInsets i.e bottom, top, right and left will be “0”. View Insets bottom offset will be non zero when the keyboard is up and every other offset will be zero (always). This is how to use viewInsets

//viewInsets property helps in determining weather the keyboard is up or not
//If the keyboard is up then bottom property of viewInsets will provide the
//amount of space consumed by the keyboard
final EdgeInsets viewInsets = MediaQuery.of(context).viewInsets;
if(viewInstes.bottom > 0){
debugPrint("Keyboard is UP");
}

6. viewPadding: ViewPadding is also of type EdgeInsets and is similar to padding in a lot of ways. The only difference between padding and viewPadding is bottom value of padding becomes “0” when keyboard is visible whereas viewPadding despite the visibility of keyboard has a non zero bottom value for viewPadding. Basically viewPadding remembers what the bottom padding was unlike padding which forgets bottom padding value and sets it to “0”. Everything else is same. Consider using viewPadding instead of padding for consistant layouts. This is how to use viewPadding

//viewPadding property does not forget what the bottom padding was
//even if the keyboard is visible
final EdgeInsets viewPadding = MediaQuery.of(context).viewPadding;
debugPrint("ViewPadding => Bottom: ${padding.bottom}, Top: ${padding.top}, Left: ${padding.left}, Right: ${padding.right}");

B. Data Members containing information about “User Prefrences

1. accessibleNavigation: Accessible Navigation is of type bool and returns true if screen reader service like TalkBack(for Android) or VoiceOver(for IOS) are enabled. This allows developer to craft apps that are asseccible to users with visual impairments. This is how to use accessibleNavigation

//accessibleNavigation returns if screen reader service is enabled by the user
final bool scrReaderStatus = MediaQuery.of(context).accessibleNavigation;
if(scrReaderStatus == true){
debugPrint("The screen reader service is enabled");
//DO Something
}

2. alwaysUse24HourFormat : This data member is of type bool and is related to the time format the user has configured on his device. There are two time formats 12 hour(AM/PM) and 24 hour format. If the user has opted for 24 hour format then alwaysUse24HourFormat will return true, else it will return false. This property is used when creating apps that display time on the app like a todo list app or a clock app. This is how to use alwaysUse24HourFormat

//alwaysUse24HourFormat returns true if the 
//user has opted for 24 Hour time format in device settings
final bool is24Hr = MediaQuery.of(context).alwaysUse24HourFormat;
if(is24Hr == true){
debugPrint("The user has opted for 24 hr time format");
//DO Something
}

3. boldText: Bold Text can be considered as an accessibility feature and is of type bool. If user has enabled Bold Text in device settings then the text appears enhanced or bold. If the user has enabled bold text the boldText property will return true. We can make adjustments to the TextStyles fontWeight porperty to handel such situations. This is how to use boldText

//boldText returns true if user has enabled "Bold Text"
//switch in device accessibility settings
final bool usingBoldText = MediaQuery.of(context).boldText;
//Somewere inside widget tree
Text(
'Hello, World!',
style: TextStyle(
fontWeight: usingBoldText == true ? FontWeight.bold : FontWeight.normal),
),
);

//NOTE: It will be better to change the font weight app wide using the
//themeData property of the MaterialApp

4. disableAnimations: Disable Animations property is of type bool and returns true if the user has disabledAnimations. This is usually done by users who have motion sensitivity or users who just prefer a static user interface. The good thing is MediaQuery provides a way by which we can account for such changes. This is how to use disableAnimations

//disableAnimations returns true if user has disabled animations
//in device accessibility settings
final bool hasDisabledAnimation = MediaQuery.of(context).disableAnimations;
if(hasDisabledAnimation == true){
debugPrint("User has disabled animations");
//Implement the logic to disable animations
}

5. platformBrightness: Platform Brightness is the property which developers will find using often. It tells weather user has enabled light theme or dark theme. platformBrightness returns an emum called Brighthess which has two values light and dark. It is common for users to change the system theme mode based on time of the day or just personal preference, so accounting for such changes is must. This is how to use platformBrightness

//platformBrightness helps in determining which theme is used 
//by the user
final Brightness platformBrightness = MediaQuery.of(context).platformBrightness;
if(platformBrightness == Brightness.dark){
debugPrint("System is using dark mode");
//Load the ThemeData configured for dark theme
} else{
debugPrint("System is using light mode");
//Load the ThemeData configured for dark theme
}

//NOTE: The MaterialApp provides a way to deal with platform brightness
//It has a property called as themeMode which is of type ThemeMode
//and is used to load themes based on value passed to themeMode property

return MaterialApp(
theme: ThemeData.light(), //light theme
darkTheme: ThemeData.dark(), //dark theme
//Automatically switches between dark and light theme based on "system theme"
themeMode: ThemeMode.system,
home: MyHomePage(),
);

I have covered some of the most used data members of the MediaQueryData class but this is not all of them. Be sure to check the documentation for MediaQueryData to learn more.

4. Creating Responsive layouts using Media Query.

Now, we have came a long way exploring Media Query and are familier with its in’s and out’s. This is the right time to discuss about creating responsive designs and the role of Media Query in creating responsive apps.

Media Query tells us about the device screen which is basically everything that is required to build a responsive layout. What are you waiting for, just start using Media Query and create responsive layous.

Well….yeah that’s all.

https://tenor.com/view/what-why-huh-steve-harvey-confused-gif-2486632017667011481

Look what I said is true, MediaQuery is the very essencce of creating responsive layout in flutter, but that’s not all of it. Let me explain.

So, calm down.

https://tenor.com/view/calm-down-gif-24013477

Knowing what MediaQuery is and utilizing MediaQuery for creating Responsive Layouts are flip sides of the same coin. It is esssential understanding both of them to create what is called as a “Responsive App ”, one that is visually consistent, offers optimal user experience acrross wide range of devices, adapts and uses the available space in best possible way, so on and so fourth. Covering all of that in a single blog post is not possible, but don’t worry as this is just the first part and next parts will make it way more clear.

Now, since we have included the section about responsive layouts in the blog post, lets see a tiny example that will demonstrate the power of MediaQuery in creating responsive app.

Let’s say you are coding a application and it will be used on smartphones, tablets, desktop and web as well. Thats a lot of screens to account for. How do we know which device screen the app is been used on, and how can MediaQuery help in this situation?

You can use something like this which determines the type of layout based on screen width

double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;

if (screenWidth >= 1024) {
// Desktop layout
return buildDesktopLayout();
}else if (screenWidth >= 600) {
// Tablet layout
return buildTabletLayout();
}else {
// Mobile layout
return buildMobileLayout();
}

This is one of the most used approaches while creating responsive layout, but the above code only accounts for screen width and there are a many things to consider. It’s best to save them for later parts.

Till then,

https://tenor.com/view/womens-day-stay-tuned-updated-gif-16526278

Best Practices while using MediaQuery

Media Query is a great, but using it can be expensive. Why, you ask?

Using MediaQuery.of(context) returns an instance of MediaQueryData which has multiple data members, so any change in any of the data members(properties) of MediaQueryData class results in rebuilding of the widget. Any minor change and the entire widget gets rebuilt, and this is expensive. But how expensive can it be?

To understand this, let’s say that the user has toggled the system theme from light to dark, now since brightness is a data member of MediaQueryData class this will result in rebuilding of all the widgets that depend on MediaQueryData. And if you have multiple MediaQueries used in different parts of the application, it will have significant impact on the performance of the application.

It is best to avoid the use MediaQueryData or use it in a separate widget so that only that widget rebuilds rather than the entire parent widget.

If not MediaQueryData the how do we get information about the device screen and user perferences?

Don’t worry, flutter has one alternate way.

As an example, instead of using MediaQuery.of(context).size, we can use MediaQuery.sizeOf(context), this will only result in rebuilding of widget when size property of MediaQueryData changes. This is a much better approach as changes in something like system theme will not result in rebuilding the widgets depending on MediaQuery.sizeOf(context).

Every data member of MediaQueryData class we discussed about can be retrived using MediaQuery.{property}of(context) instead of MediaQuery.of(context).{property}.

If we only need devicePixelRatio using MediaQuery.devicePixelRatioOf(context) will be better than using MediaQuery.of(context).devicePixelRatio;

Same can be said about all the other data members or properties of MediaQueryData class. We can leveragethis to minimize dependency on MediaQuery.of(context) or MediaQueryData class

Conclusion

In conclusion, MediaQuery is an essential tool for building responsive and adaptive user interfaces in Flutter. By leveraging MediaQuery, developers can create layouts that can adjust themselves to fit various screen sizes, orientations, and device types, ensuring a consistent and optimal user experience across smartphones, tablets, desktops, and the web.

I hope that this blog has provided you with the information required about MediaQuery and a overview of Responsive Design.

And for reding the entire blog,

https://giphy.com/gifs/illustration-thanks-thank-you-3ohs4kI2X9r7O8ZtoA

Building Cenlo | Helping professionals automate time tracking with absolute privacy

See more from me