DEV Community

Cover image for How to create Chrome extension using Flutter?
Narayan
Narayan

Posted on

How to create Chrome extension using Flutter?

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

and add this script inside the body

  <script src="main.dart.js" type="application/javascript"></script>
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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),
    ));
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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),
    ));
  }
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)