How to create Chrome extension using Flutter?

How to create Chrome extension using Flutter?

Enables flutter web app as extension (chromium based)

ยท

5 min read

Table of contents

No heading

No headings in the article.

What is Chrome extension ๐Ÿค”? : here

What is Flutter ๐Ÿค”? : here

So First, create โšก a project in flutter

flutter create app_name

if flutter web is not enabled, then enable it by

flutter config --enable-web

to run the flutter app on the web ๐ŸŒ, use

flutter run -d Chrome

Now we know that the config for running an app on the web is correct

Let's make some changes ๐Ÿง‘โ€๐Ÿ’ป in index.html

So, we can use the app as a Chrome extension.

In index.html remove this from the head tag

  <script>
    // The value below is injected by flutter build, do not touch.
    var serviceWorkerVersion = null;
  </script>
  <!-- This script adds the flutter initialization JS code -->
  <script src="flutter.js" defer></script>

and this from the body tag

  <script>
    window.addEventListener('load', function(ev) {
      // Download main.dart.js
      _flutter.loader.loadEntrypoint({
        serviceWorker: {
          serviceWorkerVersion: serviceWorkerVersion,
        }
      }).then(function(engineInitializer) {
        return engineInitializer.initializeEngine();
      }).then(function(appRunner) {
        return appRunner.runApp();
      });
    });
  </script>

and add this script inside the body

  <script src="main.dart.js" type="application/javascript"></script>

and also add style="height: 600px; width: 650px" inside HTML tag

it will set the extension view's height and width to 600 pixels and 650

then open manifest.json and replace all content with

more about manifest.json : here

{
  "name": "name of extension",
  "description": "description for extension",
  "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
}

and run flutter build web --web-renderer html --csp this command will generate the web build as flutter web will dynamically generate code in the build output and this gives us CSP errors in the browser we have to use --csp flag.

To learn more about the above command: doc

after this, the web build will be generated at build/web

to run ๐Ÿƒโ€โ™‚๏ธ the Chrome extension, open chrome and navigate to chrome://extensions/ and enable developer mode and click on load unpacked

open the web build from the path app_name/build/web

then open a new tab and click on the extension icon

yahooโ€ฆ๐Ÿฅณ

We made a basic counter extension in Flutter

Let's build ๐Ÿ”จ something useful ๐Ÿ‘ฉโ€๐Ÿ’ป

let's build a simple base64 encoded and decoded ๐Ÿ”

What is base64: here | how base64 works? : here

here is the full code in main.dart the UI part is simple if you are familiar to flutter

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.grey,
        ),
        home: const Base64());
  }
}

class Base64 extends StatefulWidget {
  const Base64({Key? key}) : super(key: key);
  @override
  _Base64State createState() => _Base64State();
}
class _Base64State extends State<Base64> {
  TextEditingController textController = TextEditingController();
  bool isEncode = true;
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        FocusScope.of(context).unfocus();
      },
      child: Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const SizedBox(
              height: 10,
            ),
            const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text("Base64"),
            ),
            Container(
              height: 25,
              width: MediaQuery.of(context).size.width * 0.38,
              decoration: BoxDecoration(
                  color: Colors.grey[600],
                  borderRadius: BorderRadius.circular(15)),
              child: Row(
                children: [
                  GestureDetector(
                    onTap: () {
                      if (isEncode == false) {
                        setState(() {
                          isEncode = !isEncode;
                          textController.clear();
                        });
                      }
                    },
                    child: Container(
                      height: 25,
                      width: MediaQuery.of(context).size.width * (0.38 / 2),
                      decoration: BoxDecoration(
                          color: isEncode == true
                              ? Colors.black
                              : Colors.grey[600],
                          borderRadius: BorderRadius.circular(15)),
                      child: const Center(
                          child: Text(
                        "encode",
                        style: TextStyle(
                            color: Color.fromARGB(255, 225, 225, 225)),
                      )),
                    ),
                  ),
                  GestureDetector(
                    onTap: () {
                      if (isEncode == true) {
                        setState(() {
                          isEncode = !isEncode;
                          textController.clear();
                        });
                      }
                    },
                    child: Container(
                      height: 25,
                      width: MediaQuery.of(context).size.width * (0.38 / 2),
                      decoration: BoxDecoration(
                          color: isEncode == true
                              ? Colors.grey[600]
                              : Colors.black,
                          borderRadius: BorderRadius.circular(15)),
                      child: const Center(
                          child: Text(
                        style: TextStyle(
                            color: Color.fromARGB(255, 225, 225, 225)),
                        "decode",
                      )),
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(
              height: 10,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 13),
              child: Container(
                padding: const EdgeInsets.symmetric(horizontal: 5),
                decoration: BoxDecoration(
                  border:
                      Border.all(color: Colors.black, style:  BorderStyle.solid),
                  borderRadius: BorderRadius.circular(30),
                ),
                child: ListTile(
                  trailing: GestureDetector(
                    onTap: () {
                      onTap(textController.text);
                    },
                    child: Container(
                      height: 40,
                      width: 80,
                      decoration: BoxDecoration(
                          color: Colors.black,
                          borderRadius: BorderRadius.circular(30)),
                      child: Center(
                          child: Text(
                        isEncode == true ? "Encode" : "Decode",
                        style: const TextStyle(color: Colors.white),
                      )),
                    ),
                  ),
                  title: TextField(
                    controller: textController,
                    onChanged: (e) {},
                    decoration: const InputDecoration(
                        hintText: "Enter or Paste here ",
                        alignLabelWithHint: true,
                        border: InputBorder.none,
                        hintStyle: TextStyle()),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
  base64Encoder(String str) {
    const base64 = Base64Codec();
    List<int> bytes = utf8.encode(str.toString());
    return base64.encode(bytes);
  }
 base64Decoder(String str) {
    const base64 = Base64Codec();
    Uint8List decode = base64.decode(str.toString());
    return utf8.decode(decode);
  }

  onTap(String str) async {
    FocusScope.of(context).unfocus();
    String text = "";
    if (isEncode == true) {
      setState(() {
        text = base64Encoder(textController.text);
      });
    } else {
      setState(() {
        text = base64Decoder(textController.text);
      });
    }
    Clipboard.setData(ClipboardData(text: text));
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text("text copied to clipboard"),
      duration: Duration(seconds: 1),
    ));
  }
}

for encoding and decoding, we will use inbuilt API for conversion from

import 'dart:convert';

  base64Encoder(String str) {
    const base64 = Base64Codec();
    List<int> bytes = utf8.encode(str.toString());
    return base64.encode(bytes);
  }
 base64Decoder(String str) {
    const base64 = Base64Codec();
    Uint8List decode = base64.decode(str.toString());
    return utf8.decode(decode);
  }

and the onTap function, first convert the string using the above function from textController and then copy the text into the clipboard Clipboard.setData(ClipboardData(text: text));

from import 'package:flutter/services.dart';

  onTap(String str) async {
    String text = "";
    if (isEncode == true) {
      setState(() {
        text = base64Encoder(textController.text);
      });
    } else {
      setState(() {
        text = base64Decoder(textController.text);
      });
    }
    Clipboard.setData(ClipboardData(text: text));
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
      content: Text("text copied to clipboard"),
      duration: Duration(seconds: 1),
    ));
  }

quick tips ๐Ÿ’ก :

  • whenever we want to add a new change in the extension

    use flutter build web --web-renderer html --csp

    to create a new build which has new changes and after that,

  • The extension by default does not save any state, so we can use local storage like hive DB it's a NoSQL DB for flutter which actually stores data in Indexed DB in browser

Source code: here | My GitHub: here

Hope ๐Ÿคž you have found this article interesting.

Keep learning! ๐Ÿ‘ฉโ€๐Ÿ’ป | More about me: here

ย