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
- Web in practice (aka let's build something using Flutter Web)
- Building a UI
- Custom Fonts
- Working with assets
- Responsiveness ?
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.
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:
Skia - An open source graphics engine used by Google Chrome, Android, Chrome Os and may more.
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.
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.
To use Flutter web there are a few prerequisites:
Your Flutter version needs to be
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:
flutter doctor and make sure you're on a version higher than 1.5.4:
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:
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:
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.
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 (
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
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,
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.
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:
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.
In the above code, we are setting up our app using
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
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:
The top bar is similar in structure to an
AppBar(it is a leading icon, center text and a trailing search icon)
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.
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
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
If you are thinking why not add the icon directly with an Image asset like:
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:
Notice the icons path is
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.
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
To center align the text and button in the
MainAxisAlignment.center, is set as the alignment. The font family for the text uses the name we gave it in
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
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.
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.
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!
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....
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:
// TODO: INSERT CODE