Peeking Under The Flutter Covers

There are many great articles that demonstrate some of the more prominent features that Flutter offers, such as hot-reloading and cross-platform development.

In this article, however, I’m going to have a look at some of the lesser-discussed aspects of Flutter.

This article is aimed at developers, tech leads and decision makers that are familiar and comfortable with at least one of the two main native development platforms – iOS and Android.

Getting up to Speed

Diving into a new development framework can generally be broken into three separate areas – language, libraries and tooling.

Language

If you know Java (or Swift or Kotlin) moving to Dart is a relatively easy exercise. Most of the flow control statements, data types and data structures will be very familiar.

One area where the Flutter language diverges is that all UI layout is done in code. If you’ve only ever used Interface Builder this may be a little confronting, however, for Android developers who are used to defining layout in XML (and iOS developers who programmatically construct UI, or React/React Native developers) it will be something that will be easy to transition to.

I’ll talk a little more about the specifics later on, but by and large, using the Dart language should not be a major transition hurdle.

Libraries

This is the hardest part of getting up to speed.

Built-in Library

Developers will need to forget all about native platform functions and features for dealing with the various types. While the language itself isn’t particularly hard to grok, every small action developers have taken on their native platform will now have to be looked up and remembered.

It was surprising how time-consuming this is. For example, developers need to learn which specific Dart properties and functions are needed to:

  • Find the length of a string
  • Determine the index of a string in another string
  • Handle UTF-8 and unicode correctly
  • Add/remove items from a list
  • Manipulate dates (in a safe and sensible way)
  • Deal with exceptions (throwing and catching)
  • Regular expression handling
  • URL manipulation
  • Send/receive data over the network
  • Parse/create JSON
  • Many others…

While not rocket science, I found that I spent a lot of time looking up property/function names to figure out the Dart way of doing things. The built-in Dart library is large and rich – it will definitely take time to re-learn the Dart-specific idioms and features.

Dependencies

Traditional mobile dev has a number of package management tools (Cocoapods, Carthage, Gradle, Maven). Dart introduces another one. The mechanics are quite familiar in that you edit a control file (pubspec.yml), fetch the dependencies, and a file is created that contains the specific versions that were fetched (pubspec.lock).

Nothing too onerous here. However, it takes time to learn what are the typical packages that might be included in a Flutter app. For example, Dagger and Retrofit are ubiquitous in Android projects – what are the equivalents for a Flutter app? Similarly, libraries such as Alamofire and Result are extremely common in iOS apps.

Tooling

Notionally, Flutter lets you work in either Xcode or Android Studio. However, with Flutter coming from Google, there is obviously a far tighter integration into Android Studio. For example, Android Studio has support for code completion, breakpoints, image swatches, Flutter inspector, template projects, etc.

Personally, I found that developing in Android Studio deploying to the iOS Simulator and Android Emulator was an extremely efficient workflow.

If you’re familiar with Android Studio, this will be a very easy transition. Xcode users will naturally want to continue using Xcode – which I’d say is possible but not advisable, so there’ll be some cross-skilling required for iOS devs to learn Android Studio.

Articles and Self Learning

To be honest, I think the documentation for Flutter is pretty good. They’ve made a conscious effort to provide documents that are guide-based for gettings started, and reference-based for when you want to know a bit more about the specific parameters.

There are quite a few example projects that Flutter provides that demo a range of techniques. However, there is definitely a shortage of articles and example projects that discuss techniques for achieving specific goals or exploring various architectural approaches for building an app.

To be sure, there are some good articles out there, but (as expected) nowhere near the volume for native Android and iOS development. I also found that many articles or github repos are essentially just extensions of the sample projects that Flutter provides in its documentation. For example, I found it very difficult to find decent examples on animation (other than the ones that Flutter provides).

Dart

Flutter is in an somewhat awkward situation at the moment as the officially supported language is Dart 1. However, it is in the process of transitioning to use Dart 2 (it may have already done so) and provides some instructions on how to use the Dart 2 beta.

Language

I must admit I had trouble finding a concise description of the specific changes between Dart 1 and 2 (most references just point to a 10,000 line Tex file). From what I can glean though, the major change (which I welcome with open arms) is that types are now mandatory. Dart 1 is a dynamically typed language that supports type annotations. It also offers an additional mode called strong mode which enforces type safety. In Dart 2, types are now mandatory but the compiler allows them to be inferred. If you were already using strong mode in Dart 1, moving to Dart 2 will be much easier.

Sadly, though, Dart has poor treatment for nullability and optionals – at least, compared to Kotlin and Swift (and even Java/Objective-C with their nullability annotations). There are some operators that give a little bit of syntactic sugar when dealing with objects that could be null, however, the compiler won’t enforce nullability checks. There have been a number of proposals, discussions and prototype implementations dating as far back as 2011, however, as best I can tell, it won’t be introduced until after 2.0 is formally launched. This makes me sad.

As a language, Dart is very similar to Java. Many types and their behaviours look exactly the same as their Java counterparts. For example, Dart uses the same distinction between Exception and Error.

While both languages support generics (eg. List<String>), one key difference is that Dart doesn’t implement type erasure.

One thing that is missing from the Flutter implementation of Dart is dart.mirrors, which means code that relies on reflection/mirrors is not possible.

In terms of support for asynchronous activity, the language itself has first-class support for async and await statements which is really nice. It doesn’t use threads, but rather uses isolates which are similar to Erlang actors. An isolate is like a parallel worker, but doesn’t share memory or variables. You can pass data between isolates using messages and “ports”. Another nice touch is first class support for Futures and Streams.

My personal opinion is that Dart is a big step forward from Java, but a backward step from Kotlin and Swift (eg. absence of optionals and associated values in enums). All in all, though, I found it to be a relatively modern, expressive language. I’m sure there are language purists that may contradict me, but as a developer who just needs to leverage the platform to get work done, it is pleasant enough to develop with.

Virtual Machine

The Dart Virtual Machine (VM) is what is used to execute Dart code. The Dart VM is a bit tricky to describe because there are a couple of modes in which it operates:

  • Just in time (JIT) – This is the mode that is used at development time and is what underpins the hot-reloading functionality. In this mode, the Dart VM both compiles and executes Dart source code (in addition to the provision of the runtime libraries). Obviously, this is a bit slower than a traditionally compiled app, but offers a more dynamic execution.
  • Ahead of time (AOT) – Used when packaging your application for release. In this mode, the Dart source gets compiled to native machine code for the hosting platform. In this case, the VM is really only responsible for providing a set of runtime libraries… execution of the machine code is the responsibility of the hosting operating system.
  • Dart byte code (DBC) – This is where your Dart source gets compiled to an intermediate byte code (DBC) that then gets interpreted by the Dart VM. This mode is more analagous to the traditional Java/Dalvik interpreter where the VM interprets a stream of byte codes. I don’t believe this is used in Flutter.

Network Layer

Basics

At its most basic, the code to perform a GET request from a remote server looks like:

import 'dart:io';
import 'dart:convert';

var http = new HttpClient();
var uri = new Uri.https('api.website.com', '/users', { });
var request = await http.getUrl(uri);
var response = await request.close();

// now let's naively convert the raw bytes from List<int> to a String.
var responseString = await response.transform(utf8.decoder).join();
print(responseString);

This is a good start, but getting back a String is not particularly consumable by our app just yet.

Converting JSON

Building on the above code snippet, Dart offers some more built-in converters (import dart:convert) to easily transform the String to a List<dynamic> or Map<String, dynamic>.

List<dynamic> listOfUsers = JSON.decode(responseString);
print(listOfUsers.first);

Unmarshalling into Strongly Typed Objects

Although the previous two steps were easily achieved, unfortunately, Flutter has extremely poor support for converting the output of JSON.decode() into strongly typed objects.

Dart does support runtime reflection using Mirrors, however that feature is explicitly disabled in Flutter.

This leaves us with two choices:

  1. Manually write code that maps json to/from our objects ourselves. This is error prone, but relatively straightforward.
  2. Use tooling to generate code that will do it automatically. There are a couple of options here: dson, json_serializable, or jaguar_serializer. While having to generate code is a pain, Dart/Flutter provide some tooling to make things a bit easier.

All in all, converting JSON to/from strongly typed objects is a big step backwards to what most mobile developers are used to with tooling like Android’s GSON/Moshi/Retrofit and iOS’s Encodable/Decodable.

Android vs iOS

One of the great benefits of using Flutter is that you can actually write code once and have it run on multiple platforms. However, it comes at the expense of honouring the native platform interface conventions.

With Flutter coming from Google, it should be no surprise that the design language and widget implementation is heavily slanted towards Material Design. By default, a new project defaults to using MaterialApp which is a bit confronting for iOS users.

Both Apple and Google have spent many years defining both a design language and implementation for how components, screens and transitions are used within an application. Flutter steps away from these patterns in two key ways:

Ground-up Implementation

Because Flutter’s implementation controls the entire rendering pipeline, everything you see on your screen is drawn by Flutter (including animations). As hard as Flutter tries, they cannot guarantee that the widgets and behaviours are the same as the native counterparts. When the operating system changes the way native components are rendered, Flutter apps do not naturally change in the same way native apps do.

If your app is a totally custom design that does not conform to Google’s Material Design or Apple’s HIG, this will not be a problem. However, if your application adopts native platform conventions, this will be a major sticking point.

Platform-specific Widgets

Flutter offers many standard widgets that are platform agnostic. They also offer a set of Android- and iOS-specific widgets.

This sounds really appealing, however, I have two concerns about this approach:

Parity between iOS and Android

At the time of writing, there are only 13 iOS-specific widgets (there are three times as many Android-specific widgets). Given that Google is driving Flutter, I totally understand that it is likely to be Android-first, however as someone responsible for shipping apps with high fidelity across both platforms it remains a concern that iOS doesn’t appear to receive the same level of attention as Android.

A perfect example of this is that the Cupertino widgets do not contain any support for themes, whereas the Material Design widgets contain full support.

Commonality

In order to use iOS or Android specific widgets, they must be constructed explicitly. ie. you don’t just ask Flutter for a “button” and Flutter decides whether you get a RaisedButton or a CupertinoButton. Instead, you explicitly need to instantiate instances of the platform-specific widgets. Another example is AlertDialog vs CupertinoAlertDialog.

The major consequence of this is that, if you intend to use widgets that conform to the platform’s natural design language, you will need to build two User Interfaces for your app. In theory, you can build a single screen and have your app choose which widget is going to build, however your code will either end up being littered with if conditions or you’ll need to move to some sort of factory pattern that abstracts some of that away.

In addition to having to duplicate your user interface, it is also important to note that neither RaisedButton or a CupertinoButton share any common type ancestry other than StatelessWidget, so there’s no notion of just passing in the button to a function that can do common behaviour.

User Interface

Image handling

As expected, Flutter has support for supplying images based on different pixel densities. They do this using what they’ve dubbed “variants”, which is essentially a fancy name for the existing Android pattern of having sub-directories that contain images with the same name as in the root folder. For example, the following file hierarchy shows how Flutter deals with providing an image for various pixel densities:

./assets/share.png
./assets/2.0x/share.png
./assets/3.0x/share.png

The above example represents a single image asset called share.png that has different files for pixel density ratios of 2.0(xhdpi) and 3.0(xxhdpi). This file can be referenced from code by using code like the following:

// return image based on pixel density of current device
var image = Image.asset("assets/share.png");

Note that they’ve moved towards using an explicit ratio. In the case where there is no exact match, Flutter will choose the ratio that is the closest.

Adding images involves explicitly adding them to pubspec.yml and making sure the images are located in the appropriate directory and named correctly. This is quite similar to Android, but a bit of a step backward from using Xcode xcassets.

At the moment, Android Studio doesn’t show images in the gutter (like they do for an Icon), which is a shame.

Also, you run the risk of having typo’s in your image name (unlike Xcode’s #imageliteral). However, I expect that the IDE will eventually get around to supporting these nice features.

I’ve saved the worst for last, though… unfortunately, there is no support for SVGs. There are a couple of open source attempts, but not having first class support for at least one of the vector image formats is a sad story.

Layout management

Flutter’s layout system using an (almost) declarative custom constraint-based framework that is somewhat similar to iOS’s Autolayout and Android’s ConstraintLayout.

An example (taken from the Flutter documentation) of the code to render a small section of a screen might look like:

Widget titleSection = new Container(
  padding: const EdgeInsets.all(32.0),
  child: new Row(
    children: [
      new Expanded(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            new Container(
              padding: const EdgeInsets.only(bottom: 8.0),
              child: new Text(
                'Oeschinen Lake Campground',
                style: new TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
            new Text(
              'Kandersteg, Switzerland',
              style: new TextStyle(color: Colors.grey[500]),
            ),
          ],
        ),
      ),
      new Icon(Icons.star, color: Colors.red[500]),
      new Text('41'),
    ],
  ),
);

You’ll notice that the layout objects (Row, Column, Expanded, Container, etc) and rules are interspersed within the components themselves (Text and Icon). This is because Flutter’s layout system is built on top of the Widget concept. Layout components are just another part of the widget tree.

Flutter comes with a bunch of bunch of built in layout widgets that let you control things like:

  • Padding
  • Alignment
  • Size
  • Aspect ratio
  • Transformations
  • Flowing
  • Grids
  • Tables
  • Offscreen buffering

One thing that I haven’t been able to find is a way that I can create relative constraints between two widgets. For example, imagine I had two Text widgets and I wanted the first one to be, say, 50% of the width of the second one. If the widgets were peers in the same node of the widget tree, I can see how I might be able to construct my own custom container that defines this layout relationship, however, if the widgets were in scattered in different locations within the widget hierarchy, I feel like it would be much harder. I guess I’d probably start with MultiChildRenderObjectWidget, but it certainly doesn’t look easy.

Animations

Flutter has support for several types of animations out-of-the box. It broadly breaks animations down into two general categories: tweening and physics-based.

Tweening

Tweening is essentially animation between two values. For example, between a height of 50 and 100, a location of (5, 5) to (10, 10), green to red, etc.

Like most animation libraries there are quite a few ways to achieve the same objective. Basically, though, animating in Flutter means externally managing the timing (using something like an AnimationController) and periodically calling setState() in order to force a rebuild.

At first glance, this personally feels a little awkward in that the sequencing of the animation is defined in a different place to the actual application.

Two good articles on Medium give some concrete examples on how to apply some custom animations to your widgets: part 1 part 2

Fortunately, Flutter provides some native transitions that hide some of the boilerplate code, such as SizeTransition, RotationTransition, FadeTransition, and so on.

Even with all that though, I feel like there’s still a little bit of state management (or superclassing) required to perform animations. I’m yet to find a simple, boilerplate-free Flutter version of something like:

UIView.animate(withDuration: 1.0, delay: 0.5, options: .curveEaseOut) {
  // animate a property
}

Physics Based Simulations

There are a number of simulations that can be used to generate animations with springing, friction and gravity. I must admit, though, I haven’t spent much time investigation these in detail – however, I do know that these simulations are used to provide the iOS-specific scrolling feel.

Writing Custom Components

Flutter heavily promotes component creation by composition. To this end, it is very easy to create new widgets, override the build() function, and away you go.

You can also create “paintable” components that allow you to totally customize the drawing using a canvas that is passed. A very simple example of this that simply paints a red rectangle looks something like:

class RedRectangle extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final rect = Offset.zero & size;

    final paint = new Paint()
      ..color = Colors.red[400]
      ..style = PaintingStyle.fill;

    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(RedRectangle oldDelegate) => false;
}

Interestingly, instances of this type cannot be embedded directly in the render hierarchy as they do not subclass Widget. You need to use a CustomPaint widget to wrap them:

Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: const Text("Demo App"),
    ),
    body: new Container(
      child: new CustomPaint(
        painter: RedRectangle(),
        size: new Size(200.0, 100.0),
      ),
    ),
  );
}

Initially, I thought this was a bit cumbersome, but after a bit more digging I found that you can also add additional children to the CustomPaintwidget. Being able to provide your own painter to the widget tree opens up a huge range of possibilities.

Themes

As mentioned earlier in this article, Flutter “supports” the notion of themes – as long as you only use Material widgets. If you use iOS widgets, or write custom components, you have to do theming yourself.

A Theme is, like most UI-related elements, just a widget. By default, the MaterialApp wraps the app within a Theme which will be used by the Material widgets from that point on. At any point, you can have a nested theme from that point forward by wrapping components within your own (perhaps modified) theme.

An example of how you can override the colour of specific widgets is shown below:

body: new Center(
  child: new Container(
    color: Theme.of(context).accentColor,
    child: new Text(
      'Text with a background color',
      style: Theme.of(context).textTheme.title,
    ),
  ),
),

Even though the Theme classes are part of flutter/material.dart, in theory, I’m pretty sure it would be possible to extend so that you could still wrap your non-material widgets in a Theme widget. However, as I mentioned before, applying the various theme settings would need to be done on a widget-by-widget basis.

I’m quite disappointed in the decision to only provide theming to Material widgets. Make no mistake, theming across multiple platforms is difficult, however, it would have been nice to at least have the theming infrastructure implemented in a common way even if they only provided a Material implementation.

Tablet support

Unfortunately, I don’t believe there is strong support for iPads and tablets. Doing a search of the flutter codebase, I found less than a handful of lines of code that specifically mention iPad or tablet.

Adaptive Layout

One part of offering tablet/iPad support is adaptive layout and one strength of Flutter is its support for flexible layout. I have a lot of confidence that an individual screen can be coded that expands to fill the available space.

Tablet-Specific Layout

However, these types of devices have different navigational patterns and layouts should be changed in order to make the user’s experience as seamless as possible.

Android offers this capability by using custom layout folders based on qualifiers such as width/height/etc (eg. res/layout-w600dp) which will automatically take into account system decorations and whether the user is in multi-window mode. Similarly, iOS has strong support for this using Size Classes, which native components such as UISplitViewController will automatically recognise.

Writing widgets that are adaptive based on screen size in Flutter is definitely possible but as far as I can tell, there’s no built-in widgets that offer this functionality immediately.

Dependency Management

Dependency management in Flutter is managed using the Pub Package Repository. You can add dependencies by adding them to the pubspec.yaml file in the root of your Flutter project. This file, among other things, controls the list of dependencies that will be installed when you run flutter packages get.

An example of the dependencies section for a very basic app might look like:

dependencies:
  flutter:
    sdk: flutter
  collection: 1.14.6
  device_info: 0.2.0
  intl: 0.15.5
  connectivity: 0.3.0
  string_scanner: 1.0.2
  url_launcher: 3.0.0
  cupertino_icons: 0.1.2
  video_player: 0.4.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_driver:
    sdk: flutter

The dependencies section describes the specific packages (and the version number) that will your Flutter app is dependent on at runtime. Like most packaging systems, you can specify a range of version number constraints depending on how strictly you want to control the version number of your dependencies.

You can also define dev_dependencies which are for dependencies that are only needed during tests (or perhaps for build-time processes / code generation).

Once packages have been fetched from the remote location, they are generally stored in $HOME/.pub-cache/hosted/pub.dartlang.org.

In terms of what files should be committed, the Dart folk have a pretty good page that describes what the various pubspec files are and which should be checked in.

If your development practice is to commit dependency graph artifacts into your app’s repo, it is a bit tricky to support. I haven’t found a specific technique that works yet, but as best I can tell, you’ll have to fiddle with using a path directive in your pubspec.yaml file and manually move files from $HOME/.pub-cache folder.

Bridging to Native

Calling Native Code

You can call back to native code quite easily using a platform channel. Essentially, this is just an RPC-style call where you can invoke a “remote” method passing some optional parameters.

At its simplest, this looks something like:

const nativeChannel = const MethodChannel("my.app.com/mychannel");

try {
  final int result = await nativeChannel.invokeMethod("someNativeFunction");
  print(result);
} on PlatformException catch (e) {
  print(e.message);
}

and then on the native side, you’d have code that looks like one of the following:

Swift

@objc class AppDelegate: FlutterAppDelegate {

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // ...

    let channel = FlutterMethodChannel(
      name: "my.app.com/mychannel",
      binaryMessenger: controller)

    channel.setMethodCallHandler { (call, result) in
      result(Int(123))
    }

    // ...
  }
}

Kotlin

class MainActivity() : FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    // ... 

    val channel = MethodChannel(flutterView, "my.app.com/mychannel")
    channel.setMethodCallHandler { call, result ->
      result.success(123);  // return 123
    }
  }
}

You can use a similar technique to invoke methods from your native code back into Flutter code. Asynchronicity is supported in both directions.

There’s very little information that I can find on the relative performance of calls through these channels. Without writing exhaustive performance tests, my impression is that the performance overhead is imperceptible for the occasional call, however, I wouldn’t be wanting to make thousands of calls in a tight loop.

It is worth noting too, that you can create a Flutter plugin that lets you write a Flutter interface that bridges across into native code.

Launching Native Transitions

You can launch native transitions (eg. a Kotlin Activity or Swift UIViewController) by using a platform channel (per above) and manually starting an activity (using an Intent) or pushing to new view controller.

While technically functional, I find this to be a really awkward handoff. Essentially, Flutter is just deep-linking into your native app. If you just need to display a single screen, this will work fine however if you have a mixture of Flutter/native, I can see this creating all sorts of navigational/experience challenges.

Embedding Native Widgets

As far as I can gather, there is no capability to embed a native component (ie. a subclass of UIView, Fragment or View) within a Flutter widget. On one hand, I can see how this would be extremely difficult given that most native components expect to be running within the context of a native run loop. On the other, it means that the extensive open source libraries of native iOS/Android UI components that have been written (and battle-tested) over the past few years are not available for use within a Flutter app.

I can see there is a proliferation of open source libraries being released every day that target Flutter, however it will be quite some time until these components are as rich and well-tested as the current plethora of iOS/Android open source components.

One thing that I have noticed is that many open source Flutter widgets only target a single platform, depending on the author’s familiarity with one or the other. This is totally understandable as many devs are more comfortable/knowledgable in a specific platform, however, it is a question that consumers of Flutter open source libraries will constantly need to be asking.

Limitations

At the moment, there are a number of coarse grained limitations that I’ve discovered along the way. I’m sure that some of these will be solved at some stage, but at the moment, a couple of the bigger ones that I’ve found are:

  • No flutter code can run in the background. This means there’s no Alarm Manager, Geofencing, or UIBackgroundMode. Now, you can always add this code to your respective native codebase, but for now, there’s no Flutter equivalent.
  • 3D OpenGL is not supported.
  • You’re on your own with ARKit and ARCore
  • Native integration into platform services (eg. HealthKit, Google Fit, Apple Pay, Android Pay, et al) are either not implemented or are unlikely to ever be
  • Maps are not supported
  • As mentioned earlier, vector graphics are not supported.

Secure Storage

Out of the box, Flutter doesn’t provide any libraries that can access iOS’s Keychain or Android’s Keystore. There is one particular 3rd-party plugin that does provide access, but the API is basically a very simple key/value store.

Testing

One of the great things about Flutter is that having a single source for the majority of your code means that you only need to write your test cases once. I’ve lost count of the number of times I’ve discovered two slightly different sets of tests for the same business logic in the comparable iOS and Android apps.

Flutter lends itself to super fast unit testing because you should only need the Dart VM to run the majority of your business logic and domain model tests.

If you want to perform UI automation testing, Flutter has a Selenium-like driver that you can use to write scripts that look something like:

test("tap on the button, verify result", () async {
  final SerializableFinder label = find.byValueKey("My label");
  expect(label, isNotNull);

  final SerializableFinder button = find.text("Press Me");
  await driver.waitFor(button);
  await driver.tap(button);

  String value;
  while (value == null || value.isEmpty) {
    value = await driver.getText(label);
  }
  expect(value, isNotEmpty);
});

Tests can easily be executed from the command line (and thus from a CI environment) by using the following command:

flutter test test/my_tests.dart

When running the tests from within Android Studio, the results are presented using the standard test runner UI which is quite nice.

Accessibility

Flutter has some initial support for accessibility, however the documentation is pretty thin.

Following on from the initial implementation, there are a few outstanding tasks before it could be used in anger.

At the moment, some widgets (eg. Icon) have built-in support for a property called semanticLabel which can be used to provide TalkBack and VoiceOver text. For thos widgets that don’t support a built-in property, you can always wrap it in a Semantics widget. For example, below shows how to wrap a CircleAvatar inside a Semantics widget:

new Semantics(
  label: 'View John's Profile',
  child: new CircleAvatar(
    backgroundColor: Colors.red,
    child: new Text('John'),
  ),
),

While it is great that you can wrap any widget to provide some accessibility assistance, having to add that extra layer for every widget is a discipline that I don’t think will extend to most apps.

Internationalisation

Internationalisation(I18N) and localisation(L10N) means many things to many people.

In terms of providing localised content for your app, Flutter has lots to offer as most of its I18N and L10N is provided by the Dart intl package.

For Android, the supported list of built-in locales is relatively modest but I expect that to grow over time.
Sadly, iOS again takes a back seat to the Material Design widgets. Most of the Material Design widgets have some level of built-in localisation, however, I cannot see any localisation logic for the equivalent Cupertino widgets. This makes me sad.

Initially, I thought that the Dart intl package provides pretty solid support for ICU date handling… however, upon researching a bit more, it seems that they do not support timezones. Note that this issue has been around since 2012… wut?! Not only that, but the documentation for DateTime.parse() says:

If a time zone offset other than UTC is specified, the time is converted to the equivalent UTC time.

In today’s world, where apps are used 24×7 all over the world, having no timezone support is bad enough, but to knowingly return incorrect data is simply horrible.

Editors note: I would love to be wrong about this. Please let me know if this is the case.

Packaging and CI/CD

The build process for producing a deployable artefact is slightly different for each platform.

Android

Simply run the following command:

flutter build apk

Under the covers, this just runs gradlew assembleRelease. For a super simple app, this produces an APK that is about 8MB, of which about 2.5MB is in the icudtl.dat localisation file and it looks like the flutter engine libflutter.so takes up about 3.5MB.

iOS

The iOS build instructions are a bit unusual. They suggest that you need to first run flutter build ios and then go into Xcode and manually create an Archive. This is great for “Hello World” apps, but is not a workable flow when you’re using a CI server to produce automated builds. Until this gets resolved it looks like the iOS build process consists of running flutter build ios, followed by a subsequent call to xcodebuild.

Interestingly, I can see that flutter build ios is actually running xcodebuild under the covers and it does create what looks like a sensible Runner.app package in the ./build/ios folder. I’m not sure why they don’t go the extra step and create the ipa file.

The world’s simplest Hello World app creates an un-thinned IPA that is about 60MB. About 30MB of that is the standard Swift library, 20MB is in Flutter.framework, and the remaining 10MB is in App.framework which looks like it contains the compiled Dart code.

Interestingly, the contents of the Runner.app/Frameworks/App.framework/App framework leaks a heap of internal information about the build machine. For example, if I run strings App.framework/App, I see stuff like:

...
file:///Users/edwardsc/dev/flutter/packages/flutter/lib/services.dart
file:///Users/edwardsc/dev/flutter/packages/flutter/lib/src/widgets/transitions.dart
file:///Users/edwardsc/dev/flutter/packages/flutter/lib/src/material/time.dart
file:///Users/edwardsc/dev/flutter/packages/flutter/lib/src/painting/decoration.dart
...

The contents of Flutter.framework/Flutter framework is a bit more benign and appears to contain the flutter engine.

Aside: It bothers me more that it should that one of the flutter build commands uses the artifact extension apk and the other uses the platform name ios. Consistency matters. But I digress…

Multiple Flavors

In theory, Flutter supports different flavors by passing --flavor xxx to the flutter build command, however it doesn’t look like it works properly for iOS (works fine on Android – it just runs gradlew assembleXxxRelease).

For iOS, the recommended way to use a scheme is to pass the scheme name in the --flavor parameter (not sure why there isn’t a --scheme parameter). The problem is that it doesn’t use the configuration that is defined in the scheme, but instead looks for a configuration with the same name as the scheme with a -Release suffix. So, if you run flutter build ios --flavor xxx then you must also have a configuration called xxx-Release.

I’m sure this situation will improve eventually, but from what I can tell, there aren’t any concrete plans to address this. If you use schemes/configurations to manage your Xcode build config (which many apps do), this leaves things in a state of uncertainty.

Push Notifications

Generally speaking, there are two types of notifications: remote and local.

Native remote notifications are caught by the wrapping native application and will need to be passed into the Flutter components using a channel as described above. Alternatively, there is a Firebase plugin provided by Flutter that does a lot of the heavy lifting for you.

There is no native support for local notifications within the Flutter SDK, however, there are several open source plugins.

Documentation

The Flutter docs are pretty good. Not only do they provide loads of getting started guides and tutorials, they also provide a jumping-off point to the reference documentation for the SDK itself – which, depending on where you look, is also well documented. There are a few examples, though, where the details are pretty slim. All in all, though, I found the documentation to be good.

What is missing, though, are the plethora of blog posts that are available for iOS/Android detailing specific problems that have been encountered and solved. To be sure, there are some good blogs however, given the relative age of the Flutter community, it will take time for these types of articles to appear.

When I’m searching for examples of how to use a particular component, I often just search github.com for projects where others have (hopefully) done something similar. What I found when researching many of the Flutter widgets, though, was that the vast majority of github projects were just clones of the example projects offered by the Flutter team.

The last thing that just isn’t there at the moment are articles describing alternate architectural patterns and how/where they are (or are not) appropriate. For example, there are many iOS posts contrasting MVC, MVVM, Viper, et al. I’ve seen very few of these so far.

I’m sure this will improve over time, however, at the moment there is a definite need for additional blog posts.

There is also a gitter community and a google group that offers some level of support too.

Supportability

One of the things I’m very conscious of is how apps built with Flutter will be supported in the future. For example:

  • Will Google even support Flutter in a years time?
  • Flutter is evolving very quickly (which is great), however, what is their backward compatibility story? Will an app written today compile in a year’s time? Swift already has a similar problem at the moment, and it isn’t evolving as quickly as Flutter.
  • If I have someone from my team build an app using Flutter, and then someone else has to pick up new features afterward, how is that handoff going to work? How hard will it be for me to cross-train developers?
  • At the moment, iOS developers have a lot of experience in the native UX patterns of an iOS app, just like Android developers do for their platform. If I develop an app using Flutter, are my developers expected to have deep knowledge of the UX patterns for both iOS and Android – as well as knowledge of Flutter. How hard will it be for me to find developers that satisfy these skills, or are motivated to learn them?
  • What is the current skills market like? At the moment, both iOS and Android are beginning to become commodity skills and developers generally have many apps under their belts that contribute to their knowledge pool. How hard is it going to be to find similar developers for Flutter?
  • I work for a digital consultancy. Choosing Flutter for a client app over the native platforms is an important technical decision that could have long-term impacts on my client’s technical stack.

Code reuse

If I think about how an app would be built for two platforms, there are five main components:

  1. Object model, business logic, network unmarshalling, error handling, validation rules, et al. This is the “functional core” that Gary Bernhardt talks about in his Boundaries talk.
  2. Pieces of screens that are common across iOS and Android. For example, imagine you have a screen where the user can update their profile details. The contents of the screen itself (not the chrome outside or the transitioning to/from the screen) are very often exactly the same.
  3. iOS-specific transitions, animations, and patterns
  4. Android-specific transitions, animations, and patterns
  5. Unit tests

Out of these five items, Flutter has an excellent code reuse story for three of them (1, 2, and 5). The exact percentage that these three items represent within your app will vary, but it is almost certainly non-trivial. Having to only build those components once is definitely a time saver – leaving only the platform-specific components to be individually developed.

Note that if your app doesn’t conform to native platform conventions (eg. it is a game or a highly branded UI), then you could argue that even items 3 and 4 could benefit from code reuse.

TL;DR

Ten Point Summary

Things I Like

  • This isn’t just something that has been smashed together. It is an engineered solution.
  • I love the react-style declaration that is used for widget definition
  • Hot loading is amazing!
  • Being able to reuse object models, unmarshalling, network handling, and all of the tests that go with it is excellent.
  • Dart 2 is a strongly typed language making refactoring much easier

Things I Don’t Like

  • Cross platform SDKs mean a dip in native platform convention fidelity. UI components that are built from the ground up are just not the same as native components.
  • Poor iOS support. There are so many things where support for iOS is just tacked on as an after-thought.
  • Supportability is a big question mark that will rule out a bunch of scenarios
  • Network unmarshalling is a big step backwards to what we’ve been used to with Codable and Retrofit.
  • Lack of timezone support. While seemingly a single micro-issue, this really bothers me.

Would I Use It?

As always, “it depends”.

Apps I Wouldn’t Use It For (yet)

  • A flagship app that is going to be extended and maintained for years to come. I’m still very wary of the support burden.
  • Apps where following native UI/UX conventions is important.
  • Apps where my client is conservative with their tech stack approach.
  • Apps that have high native interaction (HealthKit, ARKit/ARCore, etc). I would, however, consider in-app purchasing and just bridging between the native code and Flutter.
  • An app that only needs to be built on one platform, although, having said that some of the productivity gains (React-style widgets + hot loading) may swing me in the other direction once I have a bit more experience.

Apps I Would Consider Using It For

  • Apps that need multiple platforms, but have a limited budget.
  • Brand-heavy apps that eschew native platform conventions in favour of a custom UI
  • Proof of concept apps
  • Short lifetime apps that need to be built quickly, but not necessarily maintained
  • My personal projects
  • Games

In Closing

Wow, this turned out to be much bigger than I thought! Hopefully I’ve covered a lot of topics that we should be thinking about when considering a new technology such as Flutter.

Of course, a lot of my summaries are subjective and prey to my personal biases and preferences. I do hope, though, that all of the information I’ve discussed is accurate. Please let me know if I’ve missed something or if you have thoughts you’d like to share.

I can’t wait to see what Google announces in a few weeks at Google I/O!!

Leave a comment



David Ford

5 months ago

> Writing widgets that are adaptive
> based on screen size in Flutter is
> definitely possible but as far as
> I can tell, there’s no built-in widgets
> that offer this functionality immediately.

Have you seen this (from the Flutter wiki):

Creating Responsive Apps
https://github.com/flutter/flutter/wiki/Creating-Responsive-Apps

David Ford

5 months ago

> embed a native component
> (ie. a subclass of UIView, Fragment or View)
> within a Flutter widget.
Looks like someone at Google is working on this:

Support inlining Android/iOS views
https://github.com/flutter/flutter/issues/19030

logo-outline

Contact Info

Sydney
Level 1 6 Bridge Street, Sydney, NSW, 2000

Melbourne
Level 1 520 Bourke Street, Melbourne, VIC, 3000

Copyright 2018 Bilue Pty Ltd ©  All Rights Reserved