Photo by Ivan Nieto on Unsplash

From cross-platform to multi-platform (6+): my Flutter journey

Gary Chang
DigIO Australia
Published in
13 min readJan 29, 2020

--

EDIT: This article and code repository has been updated (in November 2021) since the original was published back in January 2020 due to the support for all platforms described below, being available from the Flutter stable channel.

I have been intrigued by all the Flutter announcements related to increasing support for other platforms such as the web, macOS etc. I wondered whether this new multi-platform technology could be applied to a Flutter app I’m currently working on, and the amount of effort required to make it work.

Spoiler alert: the reality matches the promise! I was able to add support for many more platforms surprisingly easily, with the current count being a total of six. All platforms support the vast majority of my original app’s functionality, with the exception of Google Maps (more on that in the sections below).

This blog summarises the experiences of enhancing a Flutter app originally built for iOS and Android, to make it multi platform capable and targeting the following platforms:

  • Android
  • iOS
  • Web
  • macOS
  • Windows
  • Linux

It’s also worth reading this blog if you’re about to start implementing a new UI experience and are yet to select an implementation technology eg. React for Web and separate iOS and Android native implementations for native app experiences, possibly some desktop experiences in future; versus a single Flutter codebase for all three (or more) UI experiences.

Features of the original app

Here are some salient features of the app I used as a basis to enhance for multi-platform capability:

  • Screens designed for a typical fixed screen sized mobile phone, not a tablet form factor
  • Retrieves and saves JSON data from / to a REST / GraphQL endpoint
  • Provides entry forms to allow a user to enter and save data
  • Displays summary information including weather forecast using SVG icons
  • Displays graphs / charts to show historical data to visualise trends
  • Remembers user selections using shared preferences
  • Displays selected locations using Google Maps

These features are quite typical of many UI experiences, regardless of whether they are for the web, native mobile or even desktop apps.

multiplat on iPhone form factor

Demo github project: multiplat

I have created a github project called multiplat that demonstrates the basics of Flutter’s multi-platform capabilities. The repository is located at:

https://github.com/flexi-creator/multiplat

Follow the detailed instructions provided in the project’s README for how to install and run the app on each of the six platforms supported.

The app is set up to fetch sample data from my-json-servier.typicode.com to demonstrate https calls across all platforms. If for any reason this endpoint fails, the demo can run instead using locally generated random data by editing data_item_service.dart and changing USE_RANDOM_DATA = true.

Multiplat distills the learnings from enhancing the original app and genericizing the demo code. For instance each row in the list we display is a DataItem model object, whilst the data for the chart / graph consists of ItemHistory objects.

The sample data created by DataItemService in this demo individualises the app for the fictional business domain of Top contributors (of something unspecified!), each DataItem represents a single person who is a top contributor. The app will display their fictional biography and profile photo, and the chart represents their fictional contribution history.

The app is architected using the Provider and service locator pattern described in detail here.

multiplat on ipad form factor

The three screens of the app (list, detail, chart / graph) are responsive and will switch between single panes on smaller screened devices, or a combined screen that has the list pane on the left, the detail pane on the top right and the chart pane on the bottom right on larger screens. The single pane to multi pane transition will react dynamically to window resizing on platforms that support it ie. web and the three desktops. CombinedView determines whether to show single or multi-pane screens based on the window size returned from LayoutBuilder and comparing those values against minimum width and height limits.

The PaneInteractionService provides an example of a way for the different panes to coordinate data changes that will work across a single paned or multi paned layout.

Just a quick note on library terminology: a Flutter package is a library external to your app code that you can import to provide a particular feature you need in the app. A plugin is a special type of package that has platform specific code that is needed to perform the actual work. For the rest of this article, I’ll refer to Flutter libraries as:

  • Packages for libraries containing only Dart code
  • Plugins for libraries containing Dart + platform specific native code (eg. java, kotlin, objective C, swift, C++ etc)

The chart / graph pane ChartView is implemented using the charts_flutter package. This demonstrates that libraries built using only Dart code work seamlessly across all platforms.

Contrast this with the shared_preferences plugin which relies on platform specific code to perform the actual work. Windows and Linux are currently not supported by this plugin, so it’s been wrapped in the demo app class MultiplatSharedPrefs. This saves the selected item in shared preferences if the platform supports it (iOS, Android, Web, macOS); or in a file otherwise (Windows and Linux).

In the demo app, saving data to shared preferences allows the same selected item (in this case a person)’s details to be shown on the next app launch. Note that this is only evident when running multiplat on larger tablets in landscape orientation, web, and desktop devices.

Support for each platform resides in a platform specific subfolder immediately under the project folder, in this case the android, ios, web, macos, windows and linux subfolders under the main multiplat folder.

General Flutter cross-platform considerations

Pure Dart code just works!

The experience I’ve found is that the app and package code that does not invoke cross platform plugin code, just works with no changes required. This category of code makes up the vast majority of the app codebase. This finding was a very satisfying result and a real tribute to the Flutter development team.

Release Mode is available on all supported platforms

Standalone binaries of apps can now be built by specifying a release build (rather than say debug build) on Android, iOS, Web, macOS, Windows and Linux. This means you can ship the standalone binaries rather than the folder structures with debug and profile builds as required previously on Windows and Linux.

Multi platform support is available from the stable channel

In this blog we only discuss the following “other” platforms: macOS, Web, Windows and Linux — in addition to iOS and Android — and they are all available from the main stable channel. There is now no need to switch to a “bleeding edge” channel like beta or master channel to write apps for the six main platforms.

Other platforms not covered here are Fuchsia, and there have been experiments with Raspberry Pi etc. Details for ChromeOS can be found here.

Responsive screen design

To have the app looking more natural on platforms with a larger window and less like a mobile app (ie. on web, macOS, Windows, Linux), consider designing all screens to be responsive, starting with wrapping each screen in a LayoutBuilder widget. This widget provides the surrounding window’s dimensions. Refer to the CombinedView class in multiplat above for an example of a responsive screen, and note how simple it is. You can also use MediaQuery to find out more about the device your app is running on, for instance the user’s adjusted font size for accessibility, device orientation (portrait, landscape) etc.

Conditional code for web vs non-web platforms

Code for the flutter web target can be different to non-web targets. For instance flutter web code may need to refer to Html classes such as DivElement or Node, requiring import ‘dart:html’. This would normally result in a lint warning, but this can be suppressed by adding the line
// ignore: avoid_web_libraries_in_flutter
immediately above the html import line. To avoid confusion and possible runtime crashes between web and non-web code, I split widgets into web and non-web versions, with the same class name, but two differently named dart source files.

A concrete example of this is the need in my original app to support Google Maps on iOS, Android and web (currently I have no flutter map solution for the other platforms). On the mobile platforms google_maps_flutter plugin is used, whilst on the web google_maps is used. Please refer to the example code within the plugin links for details on who to use them. In this case, the web version requires initialisation of the map using manipulation of html elements, so it needs the dart:html import. Two dart files map_view.dart and web_map_view.dart were created, both containing the MapView widget class (but with different non-web and web implementations). The calling code should then conditionally import them:

import '../map_view.dart'
if
(dart.library.js) '../web_map_view.dart';

The flutter run or flutter build commands will now load the appropriate Dart file when the target platform is specified eg. flutter run -d web vs flutter run -d ipad. Ensure that the public constructors and accessors are identical between the two dart files. The calling code for MapView now can be completely ignorant of which platform the map code is targeted to.

Different widgets for different platforms

Whilst it is relatively straightforward to get your app running on a new platform, visually the app may not follow the norms for that platform. There are several approaches to this. If the app already presents a highly branded look and feel, there may be little need to customise the widget set for the new platform. If the app has a very native look and feel for a particular platform, it may be worthwhile adopting the other widget set eg. if the app is using the Material library, consider adopting the Cupertino library when the iOS or macOS platform is detected. This video provides a way to implement this.

Flutter Plugins and platform support

When you’re hunting around for the appropriate packages and plugins to meet your required feature set, use https://pub.dev/ to view more details about the package (which could be a plugin). Each library details which platform(s) it supports, but this information may not be exhaustive so experimentation is required to confirm platform compatibility.

For example, charts_flutter only lists support for Android, iOS and Web, however I’ve discovered it works well on all six platforms discussed in this article. Packages written in Dart only (ie. without platform specific code) such as charts_flutter will most likely work on every platform.

You may need to import several unrelated plugins to give you adequate platform coverage, similar to the Google maps example described above.

multiplat on Android phone form factor

Android

The android platform specific folder is created by default when you issue the flutter create <project> command.

Flutter provides the best support for Android with a complete Material design library. Most documentation including tutorials assume the use of this library, including the demo project multiplat. There are few if any cross-platform concerns with android, but do note the navigation differences including screen transitions between Material and Cupertino that is a natural consequence of the norms of those native platforms. See here for more details.

Be mindful of widgets that have totally different implementations between the platforms, such as the calendar selection widget: using the Material widget you can simply call the showDatePicker function, whereas using Cupertino you have to embed a CupertinoDatePicker inside showModalBottomSheet to more closely match native app behaviour as detailed here or just use this package that greatly simplifies selecting a calendar date on iOS devices.

iOS

The ios platform specific folder is created by default when you issue the flutter create <project> command.

For a user experience closer to the native platform, adopt the use of Cupertino rather than Material widgets, which also provide navigation and page transition norms more native to iOS. See here and here for more details.

In multiplat, search for occurrences of Cupertino* and compare to their Material* widgets, used side-by-side depending on Platform.is*.

For data entry, one inconvenience is that the iOS pop-up soft keyboard does not have any buttons for next and previous form input fields, nor a Done button when the keyboard is numeric. This necessitates the use of the keyboard_actions package that provides a widget sitting above the software keyboard to perform field navigation and form submission.

multiplat as a web page

Web

There is additional information for Flutter web.

The web platform specific folder is created by default when you issue the flutter create <project> command and you’ve already enabled web development: flutter config --enable-web

All platform specific code which is NOT for the web needs to be written using this pattern:

if (!kIsWeb && Platform.isIOS) { ... add iOS widgets to tree ... }

ie. ensurekIsWeb is always checked first because executingPlatform.is* by itself causes a null crash on the web.

Also see the section above on Conditional code for web vs non-web platforms.

The SVG library does not support Web currently (it works well on the other five platforms). A simple workaround is to use Image.network (this can be used for both remote as well as local asset based images eg. ‘https://myserver/myIcon.svg’ and ‘assets/assets/myIcon.svg’ — the latter awkwardness is required due to the way Flutter packages the assets source folder for web) when the Web platform is detected. The other five platforms support SVG images. The flutter_svg library also brings in a transitive dependency called petitparser which causes a javascript crash on Safari web browsers (Chrome and Firefox tolerate this library), so the workaround is to use a conditional import in the app class using SVG code:

import 'package:flutter_svg/flutter_svg.dart'
if
(dart.library.js) 'package:vinot_flutter/ui/shared/dummy_svg.dart';

Where dummy_svg.dart contains a do nothing implementation of SvgPicture to pass compilation.

Use the html renderer to avoid cross site issues (CORS) for network images sourced from a different host:

flutter run -d chrome --web-renderer html // run the app

flutter build web --web-renderer html --release // production build

The sections below discuss the desktop platforms. A great resource is the github repository:

This repo contains very useful examples of desktop specific plugins that have not migrated into the official pub.dev Dart package repo, such as file chooser and menu bar. There are also implementation details provided in the various READMEs that will give insight for working with each platform.

multiplat as a macOS desktop app

macOS

There is additional information for Flutter for desktop, of which macOS is one.

The macos platform specific folder is created by default when you issue the flutter create <project> command and you’ve already enabled macos development: flutter config --enable-macos-desktop

Google maps are not supported for macOS as of June 2020.

There is very limited support of macOS for plugins but Dart only packages will likely work well.

multiplat as a Windows desktop app

Windows

There is additional information for Flutter for desktop, of which Windows is one.

Enable Windows Desktop development: flutter config --enable-windows-desktop

You’ll need Visual Studio tools in order to compile and build for Windows. Follow the instructions provided from the output of flutter doctor to download the right version of VS.

The windows platform specific folder is created by default when you issue the flutter create <project> command and you’ve enabled Windows development (see the flutter config line above).

Shared preferences are now supported on Windows so file based solutions for this functionality are no longer needed.

Google maps are not supported for Windows.

Since the announcement of support for Windows on the main stable channel, the number of plugins is slowly but surely increasing for the Windows platform, however Dart only packages will likely work well.

multiplat as a Linux desktop app

Linux

There is additional information for Flutter for desktop, of which Linux is one.

Follow these instructions to install Flutter on linux.

The linux platform specific folder is created by default when you issue the flutter create <project> command and you’ve enabled linux development (see the flutter config line above).

There is now a default font specified for linux so there is no longer any need to include a font in pubspec.yaml unless you want to use non-default fonts.

Shared preferences are now supported on Linux so file based solutions for this functionality are no longer needed.

Google maps are not supported for Linux.

Since the announcement of support for Linux on the stable channel, the number of plugins is slowly but surely increasing for the Linux platform, however Dart only packages will likely work well.

The app icon is located separately to the linux app executable, unlike all the other platforms. See multiplat’s README for details on how to create a desktop icon for your app.

Conclusion

Despite the points mentioned above mostly relating to installation and particular plugins, providing support for each new platform was surprisingly easy.

In particular, since the vast majority of the app’s codebase is written in pure Dart code (using Dart only packages), it just worked with no modifications necessary across all the platforms.

After this positive experience I can recommend giving Flutter serious consideration for any future UI implementations, regardless of whether they’re for a single platform or if there are cross platform considerations.

The fact that Flutter supports mobile devices and the web from a single codebase is pretty compelling.

There are also tempting hints about future support for Universal Windows Platform so imagine deploying your Flutter app to Xbox in future, on top of all the platforms supported now. Wow!

For those who have already implemented apps on Flutter, why not try making it support your favourite desktop environment, or even make it a web app. Personally I’ve always wanted to develop Mac and Windows desktop GUI apps for fun and now I have an easy way to get started without having to learn many new frameworks and languages!

If you haven’t already, check out my other Flutter blog:
Is Flutter ready for Enterprise Mobile Apps?

--

--