DEV Community

Cover image for Make your Flutter app the Google Chrome Extension
Sahibul Nuzul Firdaus
Sahibul Nuzul Firdaus

Posted on

Make your Flutter app the Google Chrome Extension

Introduction

Lately I've been trying to emulate and create an application similar to the daily.dev application. If you don't know about daily.dev, in short, it's a platform that features news and articles about personalized technology for developers.

The last few years I learned and got used to building apps using Flutter & Dart. In this journey of mine, I was inspired by the daily.dev platform. Therefore I tried to make a Flutter application similar to daily.dev and make the Flutter application run as a Chrome extension application.

...

How do I do that? πŸ€”

Create your Flutter project or Use an existing Flutter project

You can create a Flutter project from scratch or use a Flutter project that you created earlier. Of course, to build a chrome extension with Flutter, the Flutter project must support the Web platform.

I myself have created a simple Flutter application similar to daily.dev. This Flutter app is made using Flutter 2.0

You can see on the Github repository below,

GitHub logo sahibul-nf / daily

daily.dev Chrome extension clone using Flutter




Setup manifest.json

In the manifest.json file located in the /web folder, I made changes to the file something like the following.

{
  "name": "daily",
  "short_name": "daily",
  "start_url": ".",
  "display": "standalone",
  "background_color": "#0175C2",
  "theme_color": "#0175C2",
  "description": "A new Flutter project.",
  "orientation": "portrait-primary",
  "prefer_related_applications": false,
  "chrome_url_overrides": {
    "newtab": "index.html"
  },
  "version": "1.0.0",
  "content_security_policy": {
    "extension_pages": "script-src 'self' ; object-src 'self'"
  },
  "action": {
    "default_popup": "index.html",
    "default_icon": "/icons/Icon-192.png"
  },
  "manifest_version": 3
}
Enter fullscreen mode Exit fullscreen mode

...

Setup index.html

Next in the index.html file, I made some changes namely,

  • set style height and width in the HTML tag for the size of the application display that appears when the chrome extension is clicked.
<html style="height: 650px; width: 350px;">
Enter fullscreen mode Exit fullscreen mode
  • Next, inside the body tag I add the following script tag. And comment or delete script tags that are available by default there.
<script src="main.dart.js" type="application/javascript"></script>
Enter fullscreen mode Exit fullscreen mode

More or less the index.html file looks like the following.

<!DOCTYPE html>
<html style="height: 650px; width: 350px;">
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.
    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.
    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
    This is a placeholder for base href that will be replaced by the value of
    the `--base-href` argument provided to `flutter build`.
  -->
  <base href="$FLUTTER_BASE_HREF">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="A new Flutter project.">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="daily">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>

  <title>daily</title>
  <link rel="manifest" href="manifest.json">
</head>
<body>
  <!-- This script installs service_worker.js to provide PWA functionality to
       application. For more information, see:
       https://developers.google.com/web/fundamentals/primers/service-workers -->
  <!-- <script>
    var serviceWorkerVersion = null;
    var scriptLoaded = false;
    function loadMainDartJs() {
      if (scriptLoaded) {
        return;
      }
      scriptLoaded = true;
      var scriptTag = document.createElement('script');
      scriptTag.src = 'main.dart.js';
      scriptTag.type = 'application/javascript';
      document.body.append(scriptTag);
    }
    if ('serviceWorker' in navigator) {
      // Service workers are supported. Use them.
      window.addEventListener('load', function () {
        // Wait for registration to finish before dropping the <script> tag.
        // Otherwise, the browser will load the script multiple times,
        // potentially different versions.
        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
        navigator.serviceWorker.register(serviceWorkerUrl)
          .then((reg) => {
            function waitForActivation(serviceWorker) {
              serviceWorker.addEventListener('statechange', () => {
                if (serviceWorker.state == 'activated') {
                  console.log('Installed new service worker.');
                  loadMainDartJs();
                }
              });
            }
            if (!reg.active && (reg.installing || reg.waiting)) {
              // No active web worker and we have installed or are installing
              // one for the first time. Simply wait for it to activate.
              waitForActivation(reg.installing || reg.waiting);
            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
              // When the app updates the serviceWorkerVersion changes, so we
              // need to ask the service worker to update.
              console.log('New service worker available.');
              reg.update();
              waitForActivation(reg.installing);
            } else {
              // Existing service worker is still good.
              console.log('Loading app from service worker.');
              loadMainDartJs();
            }
          });
        // If service worker doesn't succeed in a reasonable amount of time,
        // fallback to plaint <script> tag.
        setTimeout(() => {
          if (!scriptLoaded) {
            console.warn(
              'Failed to load app from service worker. Falling back to plain <script> tag.',
            );
            loadMainDartJs();
          }
        }, 4000);
      });
    } else {
      // Service workers not supported. Just drop the <script> tag.
      loadMainDartJs();
    }
  </script> -->
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

...

Build an app and let’s get a try! πŸš€

After doing some of the steps above, then I build the app with the following command.

flutter build web --web-renderer html --csp
Enter fullscreen mode Exit fullscreen mode

After the application build is complete, then open Google Chrome and go to chrome://extensions/

And enable Developer mode.

Load extension

Next, click Load unpacked and select the build/web folder which is the result of the Flutter Web application that has been built. After that, the chrome extension application will appear as shown in the image below.

Extension already to use
...

Result

Now I can run Chrome extension apps created using Flutter and it runs fine.
Awesome 🀩

...

Demo App

See a more complete video demo via the following video.

...

Ending

This was a fantastic learning experience for me πŸš€.

And finally, I can make an application that is similar to daily.dev by providing chrome extension support in the daily.dev application cloning project that I created using Flutter. Awesome 😁 🀩

Top comments (0)