Hello people!
Welcome to Ideatebox. Hope you all are doin’ great. So, my today’s article is about Flutter & the ways to build a responsive site on Flutter.
I will be sharing with you some insights & will fuel you with some effective tips & tricks that will help you to build an awesome responsive application.
So, Starting with the…….
The Problem!
In order to have a responsive application, the basic question we’re addressing today is “How can we provide all the relevant data to modify our UI at the widget level?“
The Solution!!!
The best solution to resolve this problem is to provide a BaseWidget that gives you the opportunity to create your own UI using custom Screen Information compiled by you.
Actually, we will build it into the BaseWidget instead of using the MediaQuery in every widget and view file, and create a SizingInformation object that will be accessible using the BaseWidget for every widget.
Let’s go over what we want to have in the SizingInformation and why we would like this data as well:
- Orientation: Based on the present orientation, we want to be able to easily decide which layout to display. Possible Landscape and Portrait Qualities.
- DeviceScreenType: This will represent the type of screen on which we are seen. Not the screen of the physical unit, but the display type, i.e. Tablet, Tablet, Desktop
- ScreenSize: Represents the total size of the current screen on which the app is shown.
- LocalWidgetSize: This represents the boundaries drawn by the current widget. At the widget level, it will also allow you to make smart UI choices, such as hiding text in an Icon+Text widget and increasing the size of the icon for much smaller screens instead.
Implemented
We will use two functional sets provided by Flutter to implement the functionality.
- MediaQuery: This gives us some useful details, particularly Orientation and ScreenSize. We’re going to use this to decide the first 3 points of knowledge listed above.
- LayoutBuilder: This widget provides us with a builder that also offers us BoxConstraints that the current widget accepts to use the Bounds.To construct our LocalWidgetSize value and transfer that back, we will use the boundaries of the Builder.
With that in mind, let the implementation begin.
SizingInformation
We need an enum that represents our DeviceScreenType before we can construct the SizingInformation. Creating a new folder named enums under the lib folder, creating a new file inside, device screen type.dart (naming will certainly change).
enum DeviceScreenType
- Mobile
- Tablet
- Desktop
Then create a new folder under lib called ui and inside create a new file called sizing_information.dart
import ‘package:flutter/widgets.dart’;
import ‘package:response_architecture/enums/device_screen_type.dart’;
class SizingInformation {
final Orientation orientation;
final DeviceScreenType deviceType;
final Size screenSize;
final Size localWidgetSize;
SizingInformation({
this.orientation,
this.deviceType,
this.screenSize,
this.localWidgetSize,
});
@override
String toString() {
return ‘Orientation:$orientation DeviceType:$deviceType ScreenSize:$screenSize LocalWidgetSize:$localWidgetSize’;
}
}
Now that the model is completed, we can configure the builder we will use to create our UI for us.
UI Architecture
In the UI folder called BaseWidget, we will start by creating a StatelessWidget. If a function returns a widget, the function is called the builder. The BuildContext and SizingInformation will be the parameters for the function. We’ll execute the builder for the build method and return the widget it creates.
import ‘package:flutter/material.dart’;
import ‘package:response_architecture/ui/sizing_information.dart’;
class BaseWidget extends StatelessWidget {
final Widget Function(
BuildContext context, SizingInformation sizingInformation) builder;
const BaseWidget({Key key, this.builder}) : super(key: key);
@override
Widget build(BuildContext context) {
var sizingInformation = SizingInformation();
return builder(context, sizingInformation);
}
}
Then we can set up a basic home view to see how we’ll make use of this. Create a new file named home view.dart under the UI folder.
import ‘package:flutter/material.dart’;
import ‘package:response_architecture/ui/base_widget.dart’;
class HomeView extends StatelessWidget {
const HomeView({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BaseWidget(builder: (context, sizingInformation) {
return Scaffold(
body: Center(
child: Text(sizingInformation.toString()),
));
});
}
}
We’ll set up a kit before we test, which allows us to quickly test our layouts. This will be used mainly for fast UI testing in development, most of the testing should still be on the real devices, to easily test all sizes if not possible by the emulator or the Site. We’ll add the Device Preview(https://pub.dev/packages/device_preview) to the pubspec kit.
device_preview: 0.1.9-beta
Then we’ll follow the instructions in the main file, wrap the app in the System preview and deliver it from the box with the appBuilder. Make sure it looks like your home file below.
void main() => runApp(
DevicePreview(
child: MyApp(),
),
);
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: DevicePreview.appBuilder,
title: ‘Flutter Demo’,
home: HomeView(),
);
}
}
When you’re using an Android emulator, you can see a preview like below. I’d recommend spinning an AVD sized tablet, but when it’s sized to be as large as possible, a simple emulator works fine for me.
You can rotate the device by clicking on the top left corner of the cog icon, adjust the device, and see how the UI responds in milliseconds. Now let’s proceed to gather the data and populate the SizingInformation.
Populating the data
We’ll start by going through our orientation.
var mediaQuery = MediaQuery.of(context);
var sizingInformation = SizingInformation(
orientation: mediaQuery.orientation,
);
Then we will describe the DeviceType with the top-level function stored under the utils folder in the ui utils.dart file. The way we decide the platform we’re on is by the device’s width. Since the size of the physical device is not returned by mediaQuery.size, we have to make sure we are using the correct measurements. We decide the type of screen by the device width. We will use either the width or height depending on the orientation, as the width of the screen will show differently depending on the orientation. We may use the width while the device is in the portrait to calculate the width of the device. We have to use the screen height while it’s in the landscape to calculate the unit distance.
DeviceScreenType getDeviceType(MediaQueryData mediaQuery) {
var orientation = mediaQuery.orientation;
double deviceWidth = 0;
if (orientation == Orientation.landscape) {
deviceWidth = mediaQuery.size.height;
} else {
deviceWidth = mediaQuery.size.width;
}
if (deviceWidth > 950) {
return DeviceScreenType.Desktop;
}
if (deviceWidth > 600) {
return DeviceScreenType.Tablet;
}
return DeviceScreenType.Mobile;
}
We can now get the devicesScreenType from using this feature in the BaseWidget and transfer it into the MediaQuery. We can also set the size of the screen.
var mediaQuery = MediaQuery.of(context);
var sizingInformation = SizingInformation(
orientation: mediaQuery.orientation,
deviceType: getDeviceType(mediaQuery),
screenSize: mediaQuery.size,
);
You can see DeviceType.Tablet on the screen if you reload your code now and change the scale to an ipad Air2, the right orientation, etc. LocalWidgetSize is the only thing that’s left. And for that in the beginning, we’ll need the LayoutBuilder listed.
Instead of executing and returning the builder method, we can return the LayoutBuilder as the root widget and execute and return the function passed in along with the BoxConstraints transformed into a Size object for its builder.
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return LayoutBuilder(builder: (context, boxSizing) {
var sizingInformation = SizingInformation(
orientation: mediaQuery.orientation,
deviceType: getDeviceType(mediaQuery),
screenSize: mediaQuery.size,
localWidgetSize: Size(boxSizing.maxWidth, boxSizing.maxHeight),
);
return builder(context, sizingInformation);
});
}
You’ll see the correct sizing details needed if you reload the code now. If you want to see how we create a responsive UI by subscribing to the FilledStacks YouTube channel using this configuration, you can see how I build a responsive UI.