Flutter: How to build a cross-platforms application

Flutter Desktop, Mobile, Web e Embedded — Adaptive interfaces for different platforms.

Leonardo Godde
Share! por Ateliê de Software

--

The official Flutter documentation, available here, says like this: “Adaptive and Responsive can be seen as separate dimensions of an application: you can have an adaptive application that is not responsive or vice versa. And, of course, an app can be both or neither.”

1. Responsive
Typically, a responsive app has had its layout tuned for the available screen size. Often this means (for example), re-laying out the UI if the user resizes the window, or changes the device’s orientation. This is especially necessary when the same app can run on a variety of devices, from a watch, phone, tablet, to a laptop or desktop computer.

2. Adaptive
Adapting an app to run on different device types, such as mobile and desktop, requires dealing with mouse and keyboard input, as well as touch input. It also means there are different expectations about the app’s visual density, how component selection works (cascading menus vs bottom sheets, for example), using platform-specific features (such as top-level windows), and more.

With that understood, let’s go to my case.

Description of my case:

As soon as the first more stable version of Flutter Desktop was released, I received the mission of developing a multi platform application with a “unified code base” for desktop and mobile.

At the beginning my first idea was to create a widget with a conditional structure to show a tree of widgets for each platform. Something like this:

import 'package:flutter/material.dart';
import 'breakpoints.dart';

class Responsive extends StatefulWidget {
final Widget mobile;
final Widget? desktop;
final Widget? tablet;

const Responsive({
Key? key,
required this.mobile,
this.tablet,
this.desktop,
}) : super(key: key);

static bool isMobile(BuildContext _) =>
MediaQuery.of(_).size.width < kTabletBreakpoint;

static bool isTablet(BuildContext _) =>
MediaQuery.of(_).size.width < kDesktopBreakpoint &&
MediaQuery.of(_).size.width >= kTabletBreakpoint;

static bool isDesktop(BuildContext _) =>
MediaQuery.of(_).size.width >= kDesktopBreakpoint;

@override
State<Responsive> createState() => _ResponsiveState();
}

class _ResponsiveState extends State<Responsive> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, dimension) {
if (dimension.maxWidth < kTabletBreakpoint) {
return widget.mobile;
} else if (dimension.maxWidth >= kTabletBreakpoint &&
dimension.maxWidth < kDesktopBreakpoint) {
return widget.tablet ?? widget.mobile;
} else {
return widget.desktop ?? widget.mobile;
}
},
);
}
}

And use it this way:

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

@override
Widget build(BuildContext context) {
return Responsive(
mobile: MobileWidget(),
desktop: DesktopWidget(),
);
}
}

Note: if you need to create a screen and resize responsive layouts, this widget is for you! But if you are here looking for an application with adapted interfaces, stay here…

During development, I realized that with this architecture I would have a lot of conditional structures to handle the details of each platform. The widgets were getting bigger and more complex and sometimes the widgets were so complex that the best thing to do was create a specific one for each platform (ex: list_items_mobile.dart e list_items_desktop.dart).

The idea to create an application with a unified code base was lost.

A possible solution:

The best idea in my case, and the one I will show you how to do, was separate the services layer by taking all the controllers, adapters, model, utils, constants and whatever else is not related to the interface (UI) of the projects. You can also take the theme files or even separate them into another project. Unit Tests and Integration Tests also go to this layer, but Golden Tests e Widget Tests do not.

So we will have the following organization:

  • services: Contains everything that is not related to the visual interface.
  • platform x project: Contains all the visual elements (UI) for platform x.
  • platform y project: Contains all the visual elements (UI) for platform y.

With Flutter, today it is possible to develop applications for Desktop Windows, Mac and Linux, Mobile for Android and IOS, Web and Embedded.
https://flutter.dev/

How to do:

Let’s start by creating the services layer using the template package, like this:

flutter create --template=package services
Files generated in the services directory

Now let’s create two other UI layers, one for Mobile and one for Web. If necessary, create additional layers for the required platforms.

Run:

flutter create --platforms=android,ios project_mobile
Files generated in the project_mobile directory

and after:

flutter create --platforms=web project_web
Files generated in the project_web directory

At this point you should have three small projects side by side in the main directory of your main project. Now let’s create the dependency for the UI projects to reference the services layer.

Change the UI projects pubspec.yaml like this:

dependencies:
flutter:
sdk: flutter
services:
path: ../services

For organization, all services, controllers, adapters, models, utils, constants must be created inside the lib/src folder like this:

arquivos organizados dentro de lib/src

To allow projects to access the documents that are in the services layer, it’s necessary to export them. Create a file for each available service (example: utils_library.dart).

This file should look something like this:

library utils;

export './src/services/utils.dart';
export './src/services/secure_storage.dart';

And finally, to access the service available in the projects of each platform, just import it:

import 'package:services/utils_library.dart' as utils;

In this way, your services layer is separated from the interfaces, allowing you to create projects with other platforms, avoiding many conditional structures and making your project more organized.

If you have any questions, feedback or suggestions, don’t be shy, leave a comment and we’ll be looking! :-)

Follow our blog on Medium and stay inside everything that happens in Ateliê.

Say hello to us! And join us on social networks:
E-mail: contato@atelie.software
Site
Facebook
Instagram
LinkedIn

--

--