Flutter for web & mobile development - cross-platform tips
By: Sebastian Waloszek
Tags: Flutter, Dart, Mobile App Development, Web Development, Cross Platform

Recently at Deviniti, we were in dire need of a redesign for our internal resource booking system called DIBS. The new system should have been rebuilt from the ground up with focus put on both web & mobile devices. New functionalities such as confirming a booking through QR code scanning should have been added that would leverage the capabilities of mobile phones that were not feasible while using desktop devices or laptops.

While choosing the technology for implementing the system, Flutter seemed like the obvious choice because of its cross-platform nature, but the fact that its web support was only in the beta phase seemed like a disadvantage and potential risk. As you can probably guess, the project turned out to be a huge success (otherwise we wouldn’t brag about it) without any need of cutting our feature set or any other compromises.

Now we’d like to share with you a couple of useful concepts and tips we learned along the way while trying to adjust our Flutter development process for both mobile & web devices.

Separate your web design

First and foremost, before starting the development process you should have separate versions of your design for different screen sizes. Keeping the same layout structure on widescreen devices as your mobile version will result in the interface looking “stretched” or just out of place. Also don’t think that you will be able to make the necessary changes and adjustments on the go while you implement the mobile interface. It will just result in tons of tweaking and wasted time.

Tell your UI designer to prepare a separate UI for your “narrow screen” and widescreen devices. Did you see what I did here? I intentionally didn’t use the terms mobile & web. A well-designed UI should be able to scale across a wide range of devices based on the available screen real estate without requiring many changes. If that’s the case your widescreen design could even be used for tablet devices or smartphones that are in the horizontal orientation. A thing that is often overlooked by many developers. If you don’t have a dedicated designer you could try your best in a mockup tool like Figma, which I recommend. During the design phase keep these 4 things in mind while creating different UI elements:

Will it scale easily across all devices? Is it appropriate for both touchscreen usage and keyboard/mouse in the case of the web version? Can it be generalized and used multiple times across the whole app? How would I implement it in Flutter?

So as to not leave you with only dry words here is an example of a widget that was repurposed by us across different display sizes in our design:

Widget repurposing for different screen sizes

In this case, the filters page widget was designed in a way to facilitate its reuse in both wide-screen (left) and mobile (right) versions of the app. In one case, it’s displayed as a collapsible side view, and in the other — as a separate page accessed through a dedicated button in the previous one. This way of doing things enables more consistency and ease of implementation across different display sizes.

App architecture is key

Before starting the implementation phase another point that should be addressed here is every developer’s favorite subject: app architecture. To keep things simple, we need something that has multiple abstraction layers and thus enables a high amount of modularity for our classes.

For DIBS we decided upon the well known and regarded “Clear Architecture” by Uncle Bob which enabled us to easily separate our network, data, domain, and presentation layers. As for our app’s state management, we used the BLOC pattern together with the flutter_bloc package which in our opinion is currently the best way of managing your app’s state in Flutter.

Dependency injection to the rescue

So now that we have a nice app architecture we can take a look at dependency injection. With it, we can easily inject a different implementation of our classes based on whether our app is running on mobile phones or on the web. This is especially crucial down the line when our apps become bigger and more sophisticated. For our dependency package, we have chosen kiwi. Here is the template for our injector class used to manage our dependencies:

The injections are divided in various ways based on the fact that we are running the development or production environments and if the current platform is web-based. This enables us the freedom of deciding which implementations should be used where.

Using kIsWeb and why it’s not enough

Moving on to the presentation domain of our code we needed a way of easily implementing UI elements that should differ between web & mobile versions of the app. To do that we can have a separate page body class that will represent the contents of our pages based on its state which will be determined by the kIsWeb constant. The code for the abstract class will look something like this:

Nothing complicated right? Let’s choose our settings page from DIBS as an example. We have defined 3 classes for its page body state:

SettingsPageBodyState — an abstract class that will be used as the base for the next two classes MobileSettingsPageBodyState — mobile state of the page body WebSettingsPageBodyState — web state of the page body

Here is the main page body class:

Here is the abstract page body state which should have different settings options available to be changed on the web version compared to the mobile one.

As you can see, because we defined our class as abstract and did not give buildSections an implementation. Each derived state will have to implement it on its own. Here are their relevant implementations:

As you can see, that way we can easily and in a clean way exclude certain functionalities that should only be used on a given platform.

We can also use this solution to scale our UI based on the available screen real estate right? Well, not so fast there, Sonic. Although yes, generally mobile devices have a certain aspect ratio associated with them, in the case of the web we need something that scales in all sorts of ways, and don’t forget about the tablets! That means that our UI scaling should not depend solely on kIsWeb, it needs to scale based on the screen. For this, we have 2 handy widgets that we can use in our build methods.

The first one is ScreenDependent which main functionality consists of selecting between two widgets based on whether the device’s screen is wide or not.

The next widget is the well known LayoutBuilder widget in which we can build different widgets based on the available local height and width constraints. Use this widget if you want to gain additional fine control on what widgets are being built.

Conclusion

To wrap things up, these are some of the methods we used to deliver our new resource booking system to both web and mobile devices. I hope you learned something new and found something of use.

Deviniti is your guide to the universe of digital transformation and enterprise software. Check out who we are and what we can do on our website.

If you want to learn more Flutter tips and tricks, make sure to check out other articles available on Deviniti Technology-Driven Blog: