Skip to content

Instantly share code, notes, and snippets.

@Nash0x7E2
Created May 13, 2019 02:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nash0x7E2/a2f8b815b2ecd09e2241de314a962573 to your computer and use it in GitHub Desktop.
Save Nash0x7E2/a2f8b815b2ecd09e2241de314a962573 to your computer and use it in GitHub Desktop.

Ins and Outs of Flutter Web

Last year at Flutter Live, the team announced to the world that they were working on bringing Flutter to the web. Earlier this week at Google IO, the Technical Preview of Flutter Web (formally called Humming Bird) was made publicly available to developers for testing.

Today we are going to cover four things:

  • A quick recap of Flutter
  • What is Flutter Web
  • Installation
  • Web in practice (aka let's build something using Flutter Web)
    • Building a UI
    • Custom Fonts
    • Working with assets
    • Responsiveness ?

Recap

First, the basics, if you are new to Flutter or you're casually browsing the interwebs and just happen to read this article, you might be wondering what all the hype is about.

Flutter is UI toolkit developed by Google aimed at building high quality, native mobile applications in as little time as possible. Unlike similar frameworks like React Native or Ionic, a compiled Flutter app contains no interpreted code or WebView, all code is compiled to native binary for the platform your are building for. This is all possible thanks to Dart, the language used by Flutter.

Unlike Javascript, Dart is a statically typed typed, class-based language which is able to compile Just in Time (JIT) or Ahead of Time (AOT). This behavior allows for one of Flutter signature features, Hot Reload...also known as sub-second stateful reload of your running app. If you are coming from a language like C#, Java or even Javascript, the syntax of Dart should feel very similar to you.

Smooth scrolling from Reflectly , see full article here

Flutter Web

As you might've guessed from the title, Flutter has expanded from being able to only run on Android and iOS. Today, Flutter is being developed to run on Windows, MacOs, Linux, Chrome Os, Smart displays and yes, even Web.

To understand how this is even possible, we need to take a look at Flutter architecture:

The top layer consist of the framework. This layer is purely written in Dart and contains libraries for Animation, Painting, Rendering and Widgets, the core building blocks of every Flutter UI. The Material and Cupertino libraries are also contained in this layer. The Flutter code you write sits a top this layer and typically uses widgets from Material or Cupertino. If there is something you want that's not included in these libraries, you can simply move to a layer below to either widgets or rendering and built it yourself...it's all open and Dart after all.

Below the Framework sits the engine, written in primarily C/C++ the engine contains three core libraries:

  1. Skia - An open source graphics engine used by Google Chrome, Android, Chrome Os and may more.

  2. Dart runtime

  3. Text - Engine used to render and layout text

The finally layer we care about is the embedder. This layer is platform specific and is where the surface for rendering Flutter is configured. If you're really adventurous, you can check out this article on building a custom Flutter embedder for a Raspberry Pi https://medium.com/flutter-io/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1

Flutter Web Architecture

Bringing Flutter to the Web posed the interesting challenge of replacing the current Flutter engine implementation with one compatible with the Web. To facilitate this and to ensure the main Flutter project remains completely stable, a fork of the project was made under the repo "flutter_web" https://github.com/flutter/flutter_web/. As the project becomes more stable, it will eventually be merged back into the main Flutter repo.

Since only the engine and layers under the Framework needs to be changed and be reimplemented, the core Flutter API remains mostly unchanged so it is entire possible to port the UI from your Android/IOS Flutter app to the Web.

The current implantation reimplements the Dart UI layer with bindings to the DOM and Canvas instead of Skia. This layer is then compiled to JavaScript, something Dart is more than capable of doing with it's Dart2Js (production) compiler. Elements which can't be represented using DOM elements are drawn using the Canvas.

Before we move into the section on installing it is worth mentioning that the project is in the very early stages of development, plugins are not yet supported on web, performance might feel slow at times, not all of the Flutter APIs are supported (the team plans to support all of the Flutter API eventually) and full desktop interaction is not complete. As the project matures and these issues are resolved, I may write a follow up article.

Installation

To use Flutter web there are a few prerequisites:

  1. Your Flutter version needs to be 1.5.4 and higher

  2. Version 3.0 of the Flutter plugin

Let's start with the Flutter upgrade, for this example I am going to be on channel master. Run the following line in your terminal:

https://gist.github.com/3aa321872daecb10fc3f0d2d9f399cef

Now run flutter doctor and make sure you're on a version higher than 1.5.4:

https://gist.github.com/1f04dd68371c2e2ac649def670036078

Next we need to add .pub-cache/bin to our PATH since we will be installing a global pub package:

If you are on Windows add the following line to your PATH:

C:\Users\<your-username>\AppData\Roaming\Pub\Cache\bin

MacOS/Linux add:

$HOME/.pub-cache/bin

With our cache directory successfully added to our PATH, we can now move onto installing webdev, a package which provides the build tools for Flutter on Web:

flutter packages pub global activate webdev

Once it is install try running webdev --help from the command line to make sure everything is okay:

https://gist.github.com/b2117e5356d5b431c41310b766108db1

Finally update your IDE's extensions then create a new Flutter project, this time selecting "New Flutter Web" from the options. This will generate the necessary files to build and run your project.

If we look at the dependencies, we can see we have two dependencies, flutter_web and flutter_web_ui. Both of these references the repos found on GitHub. The list of dev dependencies is also very small, only containing compilers, runner and pedantic, a package for static analysis.

https://gist.github.com/1e6de6c0b442dfba207131dfb9c6a4d4

Web in practice

Now that we've covered the theory, let's actually build something and see Flutter Web in action.

The website we are going to be building is going to be similar to this concept from Hulk Code

It is a simple yet beautiful design allows us to explore everything from layout to working with custom fonts and assets.

Create the project

First create a new Flutter web project. If you're using InteliJ, create a new project using the Dart wizard (the option is not available using the Flutter menu). For VsCode users, create the project using the "Flutter: New Web Project" from the command palette (Ctrl+Shift+P).

Project creation using IntelliJ

Project creation using Vscode

Feel free to use any name you like for the project, I am going to stick to the boring name of 'Furniture'.

When our project is generated, you will notice it creates two folder and two files:

web - Contains index.html, the entry point of the project and main.dart (used for initializing Flutter Web then running your app). Assets are placed in this folder.

lib - Contains main.dart and is where most of your code will go

pubspec.yaml - Every pub package contains a pubspec.yaml, it contains metadata and dependencies associated with your project.

analysis_options.yaml - Configures the lint rules for the project. If you would like to set your own lint options, see https://dart.dev/guides/language/analysis-options

Opening lib/main.dart shows us the default code generated by the project. To run, hit F5 or "Debug -> Start Debugging" on Vscode or hit the run button on the main toolbar if you are using InteliJ.

Step: 0: Adding our assets

If you are coming from Flutter on mobile, you first thought might be to create an assets folder in the root of the project directory, copy your files then add them to your pubspec.yaml. At the time of writing this, assets are handled a bit differently. First we need to navigate to the web folder then create a new folder, assets.

You can download the assets need for this project from here: https://www.dropbox.com/s/okjtyk04b7ucun2/assets.zip?dl=0

Unzip the folder and copy it to the web folder.

Looking inside the folder, you will notice everything is broken up into folders which is pretty normal. The black sheep in the heard "FontManifest.json" might be unfamiliar to most so let's open this file and have a look at its contents.

https://gist.github.com/6babe29b0adea536bc5121beeae8c404

As you can see, the file is essentially an array of object with each element being a different font family. The first font registered is the MaterialIcons font. This is necessary to use material icons in your project since unlike a regular Flutter project, you don't have the option to set use-material-icon: true. The other two entries in the file are for two fonts we are going to use in the project. If you would like to add more fonts to your project, simply follow the format or copy and edit one of the objects.

Step 1: Setup

Let's start by opening main.dart and removing all of the generated code and import the material library from Flutter web:

import 'package:flutter_web/material.dart';

Note: We import from flutter_web instead of flutter. This is due to the fact that some API's are still not available. Like I mentioned at the start of this post, as development continues, it will eventually be merged back into the main Flutter repo.

Next we need our main function for running our app. This function will be called by the file web/main.dart after the platform is initialized by the framework.

https://gist.github.com/577fd5dda799be80e364b9fc5b1b5d67

In the above code, we are setting up our app using runApp and MaterialApp. I am not going to explain too much about what's going on here since it is regular Flutter code. We are disabling the debug banner and setting the home screen to Furniture().

With our setup out of the way, let's create the home screen.

Step 2: Home Screen: App Bar

If we look at the reference image, we notice a few things:

  1. The top bar is similar in structure to an AppBar (it is a leading icon, center text and a trailing search icon)

  2. The actual body of the page has content stacked on top of each other. The social buttons in the bottom right "floats" about the image and text.

https://gist.github.com/d070e25351bcf2c4132833cea3e56c47

The first can easily be translated to code. The snippet above creates a StatelessWidget with a Scaffold with the background color set to white. An AppBar is then added to the Scaffold. The elevation, background color and icons of the AppBar are all set. For the text, we center it by setting centerTitle: true on the AppBar then create a text widget and pass it the String "Furniture". Some basic styling is applied using TextStyle.

Running the above code looks fine except for the drawer icon. In it's place there is an account circle...wtf?!

Don't be mad, you typed the code after all 😂 I intentionally added an Icon there as a placeholder since this is where we run into one of the behaviors that is unique to Flutter Web.

If you are thinking why not add the icon directly with an Image asset like:

Image.asset("../web/assets/icon/nav-icon.png"),

you would not be too far off from the solution but running the above code with a direct path will cause the app to crash.

To use assets in Flutter web, all paths are relative to the assets/ folder. So to add an image to our leading we would replace our icon with:

Image.asset("icon/nav-icon.png"),

Notice the icons path is icons/<file>, not assets/icon/<file>

Step 3: Body

You might look at the design and think the building the body of this site will take forever and it's super complicated. But fear not, we're going to build it in just a few lines of code.

Remove the comment // Body from our code and replace it with the method _buildBody. Methods are a great way to break up your widget tree into smaller more manageable bits. However before you start creating a bunch of method, it should be noted that this should only be done for small widget. If the code you are breaking up is relatively large and complex, a widget should be used instead. In our case it's fine.

https://gist.github.com/20da70e736f94c88147bd23cfb65d66e

The layout for the page consists of two layout widgets, a Row and a Column of text. Since the image is aligned to the left of the text and button, it makes sense that both should be placed in a row. Expanded widgets are used to give the image and column which contains the text a flex value. This value is used to tell the widgets how they are to divide the available space. Since both have a Flex value of 1, the space will be shared evenly between both widgets.

Notice we simply use the name of the image as the path. This is because our image is located in the root of the assets folder and not in a sub-directory. Remember all assets are relative to assets/.

To center align the text and button in the Column, MainAxisAlignment.center, is set as the alignment. The font family for the text uses the name we gave it in FontManifest.json.

Reloading our app now and you'll see that we're almost finished, we have a background image with some text and a button to the right of it. You can even try resizing your browser windows and notice how the image and text shirking as it is made smaller. Even though we did not write any code to handle this, it works as expected. In a later section we'll customize things even further so as the window width gets to the size of a mobile device, the layout changes.

The final piece of the puzzle is the social buttons. To create this, we need a Row of FlatButtons. Their child is going to be wrapped in a Center containing an Image.asset with the path to the appropriate asset image. We are also going to set the mainAxisSize to min since we do not want to Row to extend to the end of the screen.

https://gist.github.com/426c6549fb34d5b9c06080f9ed608279

Now all we need to do is add our buttons to the Stack and wrap them in an Align with the alignment set to bottom right.

https://gist.github.com/031f944079e55a8b81e66fdb50e12641

I added 48.0dps of padding around our buttons so they don't align to the very edge of the screen. Reload the app and you should now have a complete landing page made entirely using Flutter Web!

Responsive Layout

Okay, we've made it pretty far and we have a working website but let's take things one step further and make it so when the user resized their window past a certain point, the layout changes.

If you are a web developer, the first thing that comes to mind is MediaQuery however since we are in the land of Flutter, we can use something else....LayoutBuilder.

As a quick refresher, in Flutter, constraints are passed down to tree to widgets. We can use a layout builder to access the constraints being passed then check to see whether the are larger/smaller than a size we care about. If you are unfamiliar with Flutter's rendering pipeline, I would highly recommend you check out these two talks:

https://youtu.be/UUfXWzp0-DU

https://youtu.be/dkyY9WCGMi0

// TODO: INSERT CODE

Conclusion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment