Flutter spans to the Web… A first look at the preview version…

Difficulty: Beginner

Introduction

On December 4th 2018, during the Flutter Live in London, Google announced a future new product, called HummingBird. We were all excited and could not wait for its release date.

On May 7th 2019, during the Google IO 2019, Google finally announced the availability of the Flutter for the Web, preview version.

Of course, I could not resist and I wanted to have a look at it.

This article gives a summary of my first impressions on this outstanding product.


How to install it?

In order to have it work, several steps are necessary:

  1. Make sure you are running at least version 1.5.4 of Flutter (and Dart 2.3)

    Run the following command:

    flutter upgrade --force

  2. In Visual Studio Code, make sure to have versions 3.0.0 of both Dart and Flutter extensions

  3. Make sure to have a reference to all components in your PATH
    These necessary components are:

    • dark SDK
      ==> $FLUTTER_DIR/bin/cache/dart-sdk/bin
    • webdev
      ==> (Windows)%USERPROFILE%\AppData\Roaming\Pub\Cache\bin
      ==> (Linux)$HOME/flutter/.pub-cache/bin
      ==> (Mac)$HOME/.pub-cache/bin
  4. For Windows users, maybe a restart will be necessary to make sure PowerShell takes the new Path into consideration.


First Web Project

  1. In VSCode command palette, type:

    flutter: new web project

  2. VSCode displays a message:

    “Stagehand needs to be installed with ‘pub global activate stagehand’ to use this feature Stagehand is a Dart project generator”

    Accept the automatic installation or directly type from your terminal:

    pub global activate stagehand

  3. After you typed the name of the project and its folder location, ‘Stagehand’ generates a basic project.

  4. Then, it directly launches the command line ‘pub get’ (the equivalent of flutter packages upgrade). This loads all the necessary packages (dependencies) and stores them in your
    ’$HOME/Pub/Cache/hosted/pub.dartlang.org’ (Windows)
    ’$HOME/.pub-cache/hosted/pub.dartlang.org/’ (Mac)

  5. After everything has been downloaded and pre-compiled, the project structure is the following:

    Flutter Web Project Structure

  6. Select the /web/main.dart file and hit ‘F5’ to run the project.
    VSCode will display a warning message:
    “webdev needs to be installed with ‘pub global activate webdev’ to use this feature.”

    Accept the automatic installation or directly type from your terminal:

    pub global activate webdev

  7. Once activated, 2 warning messages are displayed in VSCode
    - “Breakpoints and stepping are not currently supported in VSCode for Flutter web projects, please use your browser tools if you need to break or step through code.”

    - “graph_inspector.dart.snapshot.dart2: tokenization, wrapping and folding have been turned off for this large file in order to reduce memory usage and avoid freezing or crashing.”

    This simply means that today, there is (still) no way of debugging the Web Application via the IDE and that we need to use Chrome Developers Tools (as with a regular web application).

  8. The Debug Console displays the following pieces of information while launching the application:

Connecting to the build daemon...
Generating build script...
Generating build script completed, took 272ms

Creating build script snapshot......
Creating build script snapshot... completed, took 9.0s

Starting daemon...
BuildDefinition: Initializing inputs

BuildDefinition: Building new asset graph...

BuildDefinition: Building new asset graph completed, took 1.2s


BuildDefinition: Checking for unexpected pre-existing outputs....

BuildDefinition: Checking for unexpected pre-existing outputs. completed, took 1ms


BuildDefinition: Initializing inputs
BuildDefinition: Building new asset graph...
BuildDefinition: Building new asset graph completed, took 1.2s

BuildDefinition: Checking for unexpected pre-existing outputs....
BuildDefinition: Checking for unexpected pre-existing outputs. completed, took 1ms

build_runner: Setting up file watchers...
build_runner: Setting up file watchers completed, took 2ms

Registering build targets...
Starting initial build...
Serving DevTools at http://localhost:52824
Starting resource servers...
Serving `web` on http://localhost:52818
About to build [web]...
Build: Running build...
Heartbeat: 1.0s elapsed, 146/163 actions completed.
Heartbeat: 2.0s elapsed, 657/671 actions completed.
Heartbeat: 3.0s elapsed, 657/689 actions completed.
Heartbeat: 4.1s elapsed, 669/704 actions completed.
Heartbeat: 5.1s elapsed, 682/713 actions completed.
Heartbeat: 6.1s elapsed, 689/717 actions completed.
Heartbeat: 7.1s elapsed, 701/717 actions completed.
Heartbeat: 8.2s elapsed, 705/717 actions completed.
Heartbeat: 9.3s elapsed, 711/717 actions completed.
Heartbeat: 10.3s elapsed, 714/717 actions completed.
Heartbeat: 11.3s elapsed, 715/717 actions completed.
Heartbeat: 12.4s elapsed, 715/717 actions completed.
Build: Running build completed, took 13.3s
Build: Caching finalized dependency graph...
Build: Caching finalized dependency graph completed, took 125ms
Build: Succeeded after 13.5s with 548 outputs (3169 actions)
Debug service listening on ws://localhost:52857/kG2ZttJ2MyA=
Font manifest does not exist at `assets/FontManifest.json` – ignoring.

Flutter Web Hello World


Hot Reload

When you are using the embedded ‘Debug / Start Debugging’ (or F5 key in VSCode), Hot Reload recompiles the outputs but does not refresh the application.

You need to manually refresh the page (via F5) on the browser.

However, if you want the Hot Reload to happen, you need to run the application, using the following command line (from Terminal Window):

webdev serve --auto restart


Let’s look at the project

pubspec.yaml

The generated pubspec.yaml is the following:

name: hello_world
description: An app built using Flutter for web

environment:
  sdk: '>=2.3.0-dev.0.1 <3.0.0'

dependencies:
  flutter_web: any
  flutter_web_ui: any

dev_dependencies:
  build_runner: ^1.4.0
  build_web_compilers: ^2.0.0

dependency_overrides:
  flutter_web:
    git:
      url: https://github.com/flutter/flutter_web
      path: packages/flutter_web
  flutter_web_ui:
    git:
      url: https://github.com/flutter/flutter_web
      path: packages/flutter_web_ui

We can notice the following:

  1. There is NO LONGER any reference to flutter. Instead, the project now depends on:

    • flutter_web
    • flutter_web_ui
  2. Rather than having to download a local version of these 2 packages, we directly reference them on GitHub

  3. During the development and in order to transpile Dart to Javascript, the project also depends on:

    • build_runner
    • build_web_compilers
  4. There are no references to any assets (I’ll get back to this later)

The /web folder

This folder initially contains 2 files:

/web/index.html

This file seems to be the starting page of the application.

Its main role is to load the application itself: “main.dart.js”.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
  <script defer src="main.dart.js" type="application/javascript"></script>
</head>
<body>
</body>
</html>

This file will have to be enriched with the usual <meta> tags.

/web/main.dart

This file is the main starting point of the flutter_web application.

Its main role is to initialize the engine, then to launch the application’s main() method, located in the /lib folder.

import 'package:flutter_web_ui/ui.dart' as ui;
import 'package:hello_world/main.dart' as app;

main() async {
  await ui.webOnlyInitializePlatform();
  app.main();
}
The Assets

When you create a new flutter_web project via stagehand, the latter does not (yet) generate any assets folder, which is a pity…

All assets such as fonts, images, … that will have to be used by the web application need to be present under /web/assets/.

Therefore, I strongly recommend that you manually create an assets folder under /web and put the following JSON file, named “FontManifest.json” in it:

[
  {
    "family": "MaterialIcons",
    "fonts": [
      {
        "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2"
      }
    ]
  }
]

Thanks to this “FontManifest.json”, you will be able to use the Icons, otherwise, they simply are not available for display.

The name of the assets directory is “assets” by default but this can be changed.

If you change it, you will have to tell the engine were to look for the assets. This is done via the ui.webOnlyInitializePlatform() call, in the /web/main.dart.

import 'package:flutter_web_ui/ui.dart' as ui;
import 'package:hello_world/main.dart' as app;
import 'package:flutter_web_ui/src/engine.dart' as engine;

main() async {
  await ui.webOnlyInitializePlatform(
    assetManager: engine.AssetManager(
      assetsDir: 'my_assets_dir'      // Name of my assets folder
    )
  );
  app.main();
}
The /lib folder

This folder contains the application itself.

Usually, there will be a main.dart file which will contain a main() method.

But, as the actual main() method is located inside the /web folder, the name of the method being called at the level of the application does not longer need to be “main()HOWEVER, whatever the name of this method, it is NECESSARY that the latter calls the runApp() method, as a regular Flutter application.


Debugging a Flutter_Web application

As said previously, you will now need to use Chrome Developers Tools (DevTools) to debug your application.

The main difference with a regular web application is that you can put breakpoints on .dart files levels. However, when you will trace the execution of the code, you will constantly navigate between .dart and .js files.

The following picture shows the exhaustive list of all the files which are loaded by the Browser at the application start. Quite substantial:

Flutter Web Network Debug


Files in Release mode

In order to launch your application in release mode, type the following line from your terminal window:

webdev serve --release

The number of files being downloaded at the application start reduces drastically:

Flutter Web Network Release


Compatibility Assessment

After this initial attempt, I wanted to see how compatible flutter_web is with existing applications, written in flutter.

To do this assessment, I considered several use-cases.


BLoCs and Assets

As you know me, I am a big fan of the notion of BLoC and I directly wanted to validate if Streams were correctly supported.

In order to assess this, I used my solution for multilingual applications (detailed explanation can be found HERE and HERE).

This solution relies on the following:

  • A Singleton class (GlobalTranslations) to handle the language selection and translations
  • A BLoC to rebuild the MaterialApp when the user selects another language
  • A Singleton class (Preferences) to handle the user preferences
  • Assets that contain the list of all translations

Observations

1. Localizations.delegate

When dealing with multilingual applications, we need to pass a series of Localizations.delegate to the MaterialApp (GlobalMaterialLocalizations.delegate and GlobalWidgetsLocalizations.delegate). These 2 delegates are used by the Flutter Framework to correctly handle the active Locale with some Widgets (Scaffold, Date Picker…).

In order to be able to use these 2 delegates, we need to add the “flutter_localizations” as a dependency to the “pubspec.yaml” file.

As this dependency relies on the Flutter SDK (which is not available when working with Flutter_web), it was NOT possible to use it.

Therefore, I could not initialize the MaterialApp with these delegates. I suppose there will be soon another solution but I could not find it at the moment.

Consequence

I could not make my MaterialApp fully adapt to the active Locale.

2. SharedPreferences

As the SharedPreferences package relies on the platform channel, it is obvious that I cannot use it.

3. Assets

In order to define the translations, I use a series of files (locale_fr.json for example).

In “regularFlutter, we define an “assets” folder, put the files inside that folder and list all the files in the pubspec.yaml, under the assets: section.

In Flutter_Web, we do not need to reference the assets in the pubspec.yaml. To define assets, you simply need to create an assets folder at the level of the /web folder. Then, simply put your files in the /web/assets/ folder.

To load the assets, you can use the usual rootBundle API as follows:

  String jsonContent = await rootBundle.loadString("locale_${_locale.languageCode}.json");
  _localizedValues = json.decode(jsonContent);
  

Difference with Flutter

flutter_web concatenates the name of the asset to the base asset directory.

As a consequence, if you are referencing an asset, named “language_fr.json”, the actual asset path will become “/assets/language_fr.json”

4. BLoC and Streams

I used the usual rxDart package and everything worked perfectly.

5. import ‘package:flutter/…’

As we are no longer using the Flutter SDK but the flutter_web,

ALL package names that reference ‘flutter/…’ need to the changed to ‘flutter_web/…

I suppose this is a temporary solution as this obliges to maintain 2 distinct source codes… maybe a solution would be to allow the pubspec.yaml to define aliases

Summary

Except for the notion of Localizations.delegate, everything worked perfectly.

Here below, you can see it in action:

Multilingual Multilingual

Animations, Overlays and Gestures

As a second example, I took my unfinished game, flutter_crush (refer to this link for further details on this game).

I took this application since it involves very complex topics, such as:

  • lots of assets (images, files…)
  • different Routes (= screens)
  • concurrent Animations
  • intensive use of Overlay and OverlayEntry
  • calculations once the drawFrame completes (WidgetsBinding.instance.addPostFrameCallback)
  • Gesture
  • Sounds

Observations

1. Sounds

I could not find a solution to play the sounds but it is also true that I did not spend too much time on this…

2. Assets

Not a single problem to load the assets, as long as you keep in mind that flutter_web concatenates the asset name to the assets based directory.

3. Routes

The handling of the Routes is exactly the same as with flutter: Navigator.of(context)…

4. Animations, Overlay, OverlayEntry, Gestures

No modifications were required. Everything worked perfectly at first attempt.

5. Rounded Corners

I found a difference in the way flutter_web renders the BoxDecoration and more specifically the borderRadius.

Whereas in flutter you can define a value of the borderRadius which exceeds the height/width of the container, without noticing any impact during the rendering, with flutter_web it seems that the value of the radius needs to be consistent if you do not want to have weird layout.

6. UnimplementedError

Some features related to the rendering (canvas) have not yet been implemented.

As an example, I will take the RadialGradient which is not yet supported in the BoxDecoration.

At time of writing this article, I found 62 occurrences of the “UnimplementedError” inside the source code of flutter_web.

7. import ‘package:flutter/…’

Same observation… you need to change all the imports related to the flutter package.

Summary

I was really amazed to see that this quite complex application directly worked without having to change too many things.

Here below, you can see it in action:

Animations Animations

Network requests

Finally I focused on the connectivity with other servers, through the use of Http requests.

In order to assess this, I took the project I used to explain the BLoC and which displays a list of movies, fetched from a remote server. (for further details on this project, please refer to this article).

Observations

I was surprised to see that the system refused to compile, because of some “transitive libraries have sdk dependencies that are not supported on this platform”.

This came from the use of the “HttpClient” (dart:io).

As I saw that the counterpart of the ‘dart:io’ was ‘package:flutter_web/io.dart’, I changed the Import statement. It then did not complain about any SDK dependency but the API was not the same…

Therefore, I had to use ‘dart:html’ instead. Then, everything worked.

Summary

I was a bit surprised to see the dependency with some kind of SDK at that level but, after a couple of minutes, everything worked perfectly as you can see here below:

Streams Streams

External Packages

At time of writing this article, it is not possible to “import” external packages that would depend on Flutter SDK.


Supported Browsers

I have tested the applications of this article on the following Browsers:

  • Chrome 74.0.3729.131 (64-bit)
  • Firefox 66.0.5 (64-bit)
  • Microsoft Edge 42
  • Opera 60.0.3255.83 (64-bit)
  • Safari 11.1.2

The tests I made worked perfectly on Chrome, Opera and Firefox. Firefox was much slower than Chrome and Opera.

On Safari (running on a Mac Book Air), the application directly reloaded as it consumed too much memory. Therefore, I could only make it run in release mode.

It did not work on Microsoft Edge.


Conclusions

I have been waiting for about 6 months to have the chance to see the flutter_web in action and… it is simply AMAZING.

In a couple of minutes, you can port your Flutter application and have it working on a Browser !!

The version I assessed is not yet finalized but already gives very good results. Some parts have not yet been fully implemented (mainly related to the canvas rendering) but this should not take very long.

Once Google will have found a solution to:

  1. Prevent from having to update the source code and change the “import” statements (from ‘flutter/material.dart’ to ‘flutter_web/material.dart’), which at the moment, forces to maintain 2 distinct source codes.

  2. Allow using the IDE to debug

  3. Allow using external packages which would have defined flutter SDK in their dependencies

Flutter_Web will become a must for all Web applications development.

Stay tuned for new articles on Flutter and … Flutter_Web.