I enjoy using Flutter, it’s easy to start a new project, easy to test, easy to profile and Dart (the language that powers it) does away with all my gripes with JS.
But as my first usage of Flutter was to make mobile apps, I’ve had trouble adapting to the newer web and desktop targets, especially when dealing with designing for larger screens.
This post is meant as a way to show a few tricks I’ve picked up and how you can use them for your projects.
This post requires you to use Flutter 1.17 at the very least with a preference for Flutter 1.18 upwards.
Starting from Mobile
As a rule of thumb I usually start designing with mobile in mind. More famous people have called this “Mobile First Design” and there a few good reason to go about it this way:
A phone screen is obviously way smaller than a computer’s screen meaning that you have to focus on the essentials, resulting in components that are self contained and easily legible.
One positive side effect of such a design is that components made for a mobile screen actually transpose well to a larger screen where they can easily be composed together as needed.
It is also important to realize that Mobile internet usage has overtaken Desktop internet usage for a few years now. According to the latest stats, even the COVID induced stay-at-home hasn’t done much to reverse the trend, which makes me believe mobile browsing is here to stay.
Writing utility code
Our app is going to very simple, containing only a Navigation Bar and the currently selected screen to be shown.
On smaller screens, our Navigation Bar will be a BottomNavigationBar while on larger screens, we are going to be using a NavigationRail.
These two widgets expect different types as their destination item. So we are going to be creating a unique Class called Destination with a few convenience methods to avoid duplication later on.
Adding getters to “transform” our Destination will let us easily map over a List of Destination later on. We won’t have to drag around fancy anonymous functions.
As you can see, both the BottomNavigationBar and the NavigationRailDestination expect an icon and a label. To keep it simple, i’ve set the label/title as a Text widget but you can go wild. Just keep in mind that you should keep a coherent design going.
Now we need something to handle the variable screen size.
Of Breakpoints and Screens
Developers that have experience with the web are used to using breakpoints for styling but a refresher helps.
As obvious as it may seem, every screen has a size with a vertical and horizontal component that are usually given in the format WIDTHxHEIGHT (some stats about the most commonly found screen resolutions can be found over here).
Because of the sheer amount of different resolutions that exist, it would be quite impossible to tailor the design to each and every single one of them.
But what we can and usually do is to take these resolutions and put them into a few groups that make sense depending on their width.
Breakpoints are just that: landmarks to divide devices by their resolution.
When doing web stuff, I like to use Tailwindcss as my CSS framework, so by force of habit, I also use their breakpoints when working with flutter. I also try to namespace my code when it makes sense.
So we’ll create a Styling class to contain our breakpoints code as well as some general placeholder styling.
Warning: If using Flutter 1.17, the Material Theme does not contain styling for the BottomNavigationBar! You’ll need to style the Widget directly instead.
Keeping all your related code in a single class like this makes it easier to modify your app later on. One single change can be echoed throughout the whole code and you won’t have to go hunting for all the references.
If you look closely at the content of the Styling class, you’ll notice that everything is declared as static so you can access the method/property without having to instantiate the containing class.
Work, Work Build, Build
Thanks to the work we’ve already done, the remaining code is pretty straightforward:
Our AdaptiveScaffold needs to be stateful since he’s going to be responsible for handling the navigation and the user inputs.
Don’t forget: A stateful widget needs to handle state changes via setState. You also do not want anything affecting your state during building.
We’ll be adding a condition in the Build method of our widget to check for the screen size using our Styling methods.
As said before, take a look at the destinations (for the NavigationRail) and the items (for the bottomNavigationBar). You can see our convenience getters be put into use. If doing something similar, don’t forget to turn your iterator (returned from map) into a List. Failure to do so will result into a compilation error.
As you can see, making a Responsive Layout in Flutter can be quite easy. Just using a few Widgets made by the Flutter team lets you get started pretty fast.
This example was voluntarily focused on how to get this responsive layout working. You’ll need to remember that the layout is not the only thing you’ll have to think about. The User experience can be vastly different depending on the platform and you might also have issues finding a plugin that works for every platform.
This post was inspired in part by a package I’ve been working on, if this was of use to you, check it out here.