Lawrence Pickford

Embedded Flutter

Flutter and Hugo

These days I work mostly in Flutter and Dart, building apps for clients and managing our build pipelines. For those who don’t know, Flutter is a framework which works on all platforms with native performance. It does this by rendering directly to a canvas instead of trying to create a bridge between native controls and dart.

Because of this it’s a fantastic tool for building apps quickly that run on different platforms and devices, though I’d stop short of saying it’s a good solution for all cases (there’s no perfect solution for all cases).

With the help of one of the guys from work and a fantastic guide by Arne Molland over on Medium explaining how to get a Flutter app embedded into a normal web page, I’ve managed to tweak and get a working implementation running in Hugo. I’d recommend reading through that post as well to get a better understanding of what we’re doing.

Hugo

This website is built using Hugo, a web framework which uses golang and markdown to create static pages at build time. I like it as I’m not a web developer, and it gives me what I want without all the messing about. It’s easy to customise to how I like it, for example by adding a new shortcode which can be picked up on by the build engine to render some different HTML.

For this little project I’d like to have a shortcode that looks like the following:

{{ < flutter-app my-app-name > }}

This shortcode would then take the app name and render a fully functional Flutter app inside an iFrame which is presented to the user. Handy for me as a Flutter dev as it means I can build simple examples of how a project works and display them directly on my website for people to check out.

The Code

Planning

So Hugo works by checking specific directories to get an idea of any custom logic. To create a custom shortcode we’ll make a file in ./layouts/shortcodes and call it flutter-app.html. This will let the Hugo know that we want a shortcode called flutter-app and that shortcode will render whatever’s in that file.

In a nutshell it consists of creating an HTML file with a body tag for the Flutter engine to hijack, copying the built flutter app into a directory somewhere in the static files of your website, and using a script to inject an iFrame onto the page with our Flutter app inside it.

The Flutter App

For this post we’re going to use the default Flutter app that’s created for any new Flutter project. Create a new project and then run the command flutter build web. Once that’s done just take the resulting build/web directory and copy it into your site’s static/flutter/apps/project-name folder. If the folder doesn’t exist then create it. Your project structure should look like the following:

Embedded Flutter File Structure

The Web Code

Next we’re going to make our HTML and script for the iFrame. We’ll take the HTML code from the Medium post earlier and modify to accept a parameter of proj, and save this in static/flutter/flutter-app.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>App</title>
  </head>
  <body>
    <script>
      const proj = /proj=([^&#=]*)/.exec(window.location.search)[1];
      const src = "apps\\" + proj + "\\web\\main.dart.js"
      
      var flutterViewScript = document.createElement("script");
      flutterViewScript.type="application/javascript";
      flutterViewScript.src=src;
      document.body.appendChild(flutterViewScript);
    </script>
  </body>
</html>

We’ll also modify the script to use the src defined in the HTML file as an input for the iFrame so that we can point it towards different Flutter apps later if we want.

class FlutterApp extends HTMLElement {
    connectedCallback() {
        const width = this.getAttribute("width") || "100%";
        const height = this.getAttribute("height") || "100%";
        const src = this.getAttribute("src");

        this.innerHTML = `
    <style>
        #app-container {
          height: ${height};
          width: ${width};
          margin: 0 auto;
          border: none;
        }
      </style>
    <iframe 
        id="app-container"
        src="${src}"
        >
    </iframe>
        `;
    }
}

customElements.define('flutter-app', FlutterApp);

The Shortcode

Now to get this into our pages with a shortcode. Open up layouts/shortcodes/flutter-app.html and let’s initialise our flutter script and then inject an iFrame onto the page.

<script src="{{ `flutter/flutter-app.js` | absURL }}"></script>
<flutter-app width="450px" height="800px" src="{{ `flutter/flutter-app.html` | absURL }}?proj={{ .Get 0 }}">
</flutter-app>

Here we’re adding our flutter-app.js script to any page that uses this shortcode and then injecting our iFrame passing it a URL with a proj parameter of what’s been passed in by the shortcode. So for us the proj parameter will be example, like so:

{{ < flutter-app example > }}

Note that if you’re implementing this yourself to remove the spacing between the curly braces and the angled braces like so {< >}.

End Result

And here’s the end result, we have a flutter app built for web embedded inside an iFrame.

This was a fun little exercise to get a Flutter App onto a webpage as an element instead of as the entire page, and for a portfolio will be pretty handy to show an app in a browser without asking a user to download to their device first.

Disclaimers

I’ve not tested how heavy apps perform inside an iFrame yet, but for simple apps and demonstrations this should be more than sufficient.

The base app is also just shy of 20mb, which could be quite a lot if you have a lot of traffic to your site. There will be ways to reduce the size of the app, but I’ve not touched on these as part of this post.

You can view the full code for this implementation (and for this website) over on my GitHub.