If you have an idea of either creating or adding web support for a Flutter plugin, but you don’t have enough experience with JavaScript or Flutter Web, then this article is specifically intended for you. We will talk about how to call a JavaScript code from Dart, how to handle JavaScript calls on the Dart side, as well as about the features in writing such a code for a Flutter web plugin in JavaScript and Dart parts.
First of all we need to understand how we can pass data between Flutter and the Web. On Android and iOS platforms we use MethodChannel to invoke native parts from a Flutter app, but on the Web to make your Dart code capable of communicating with JavaScript and vice versa we need to use the official core package called js. It provides a way to declare methods that can be called from Dart and JavaScript code.
In your Flutter plugin project add js dependency into the pubspec.yaml file (the changes are highlighted):
flutter_plugin/pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
js: 0.6.3
...
To call JavaScript code from Dart, declare a global function in the JavaScript code and then create an external
function with @JS()
annotation in the Dart code.
JavaScript code example:flutter_project/web/index.js
//Declares a global function
window.jsInvokeMethod = async (method, params) => {
//Process your Dart call here
}
Dart code example:flutter_plugin/lib/flutter_plugin_web.dart
@JS()
library callable_function;
import 'package:js/js.dart';
@JS()
external dynamic jsInvokeMethod(String method, String? params);
Finally, let’s call the JavaScript method from Dart.flutter_plugin/lib/flutter_plugin_web.dart
dynamic sendMethodMessage(
String method, String? arguments) {
final dynamic response = jsInvokeMethod(method, arguments);
//...
}
But most likely, if you add support for some native JavaScript library, you will face JavaScript asynchronous methods. In such a case to convert JavaScript Promise
into the Dart Future
, import package:js/js_util.dart
and use promiseToFuture
method.
Handle the JavaScript Promise Dart code example:flutter_plugin/lib/flutter_plugin_web.dart
//...
import 'package:js/js_util.dart';
//...
Future<dynamic> sendMethodMessage(
String method, String? arguments) async {
final dynamic response =
await promiseToFuture(jsInvokeMethod(method, arguments));
//...
}
Now let’s call a Dart code from JavaScript. For that, we also need to declare a function with @JS named annotation and then create a function that can be called from JavaScript with allowInterop
method from package:js/js.dart
. Pass to the allowInterop
method a callback function which will be triggered each and every time after JavaScript calls a jsOnEvent
function.
Dart code example:flutter_plugin/lib/flutter_plugin_web.dart
//..
@JS('jsOnEvent')
external set _jsOnEvent(void Function(dynamic event) f);
//..
//..
//Sets the call from JavaScript handler
_jsOnEvent = allowInterop((dynamic event) {
//Process your JavaScript call here
});
//..
Now let’s call Dart from JavaScript.flutter_project/web/index.js
window.jsOnEvent('Hello from JavaScript')
This is it, let’s move ahead and prepare the JavaScript code for the plugin.
The first thing to understand is that there is no need to create any folders with JavaScript code in your Flutter plugin. All JavaScript code will be either placed or imported directly in your web
folder in the Flutter project.
First of all, create the index.js
file in your Flutter project web
folder. The code in the index.js
will look something like this.
flutter_project/web/index.js
//Your JS libraries imports
//...
//Declares a global function
window.jsInvokeMethod = async (method, params) => {
//Process your Dart call here
}
//Sends event to Dart
function callDart(event) {
window.jsOnEvent(event)
}
//Your JS code
//...
Then create a package.json
file in the web
folder and put the code below there.
flutter_project/web/index.js
{
"name": "web",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"dependencies": {
"webpack": "5.28.0",
"webpack-dev-server": "3.11.2",
"webpack-cli": "4.5.0"
}
}
Finally create a webpack.config.js
file in the web
folder and put this code there.
flutter_project/web/index.js
{
"name": "web",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"dependencies": {
"webpack": "5.28.0",
"webpack-dev-server": "3.11.2",
"webpack-cli": "4.5.0"
}
}
Install the npm
if you previously did not. We will use webpack
to generate the final JavaScript bundle file which we will deploy and connect to the index.html later.
Finally, create a webpack.config.js
file in the web folder and put this code there.
Now navigate to the project web
folder and run the npm install
command:
$ cd ~/web
$ npm install
After that bundle your JavaScript files using npm run build
command:
$ npm run build
The bundle.js
file appears in the dist
folder, which you can specify in the index.html
file.
Let’s add it to the bottom of the head tag:flutter_project/web/index.html
<!DOCTYPE html>
<html>
<head>
<!--...-->
<script type="text/javascript" src="./dist/bundle.js"></script>
</head>
<body>
<!--...-->
</body>
</html>
Now you can call your JavaScript code from Dart, but let’s go a little further and deploy the bundle file to use it by HTTP link.
To make your bundle file accessible by HTTP link, put it in the GitHub repository and use jsdelivr CDN to generate a jsDelivr link from your GitHub bundle file link.
Put this link instead of the local link in the index.html
file.
flutter_project/web/index.html
<!DOCTYPE html>
<html>
<head>
<!--...-->
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/NikitaPipita/flutter_web_plugin_example@master/web/bundle/bundle.js"></script>
</head>
<body>
<!--...-->
</body>
</html>
Congratulations! Now your JavaScript bundle is accessible to use by the link.
Now let’s move to the Flutter plugin part. Add the following dependencies to the pubspec.yaml
file (new changes are highlighted):
flutter_plugin/pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
flutter_web_plugins:
sdk: flutter
js: 0.6.3
...
Also specify a Flutter plugin web entry point class and a path to it in the pubspec.yaml
.
flutter_plugin/pubspec.yaml
flutter:
plugin:
platforms:
android:
package: com.example.flutter_plugin
pluginClass: FlutterPlugin
ios:
pluginClass: FlutterPlugin
web:
pluginClass: FlutterPluginWeb
fileName: flutter_plugin_web.dart
Let’s move on and code the web entry point class. Like on iOS and Android, the web part of the plugin also needs to be registered before being used. This is done in a static registerWith
method in the web plugin class, which will be called when your web app starts.
To make your web support more compatible with previously added Android and iOS plugin realizations, let’s register previously used ones for communication with native parts MethodChannels here, in registerWith
method, using Registrar
from package:flutter_web_plugins/flutter_web_plugins.dart
. Now all MethodChannels calls will be handled by the web plugin class.
Also, this is the most suitable place to use the allowInterop
method to create a function that can be called from JavaScript.
The registerWith
method code:flutter_plugin/lib/flutter_plugin_web.dart
static void registerWith(Registrar registrar) {
final MethodChannel channel = MethodChannel(
'flutter_plugin',
const StandardMethodCodec(),
registrar,
);
final pluginInstance = FlutterWebPluginExampleWeb();
channel.setMethodCallHandler(pluginInstance.handleMethodCall);
//Sets the call from JavaScript handler
_jsOnEvent = allowInterop((dynamic event) {
//Process JavaScript call here
});
}
The full code of web plugin class is provided:flutter_plugin/lib/flutter_plugin_web.dart
@JS()
library callable_function;
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
@JS('jsOnEvent')
external set _jsOnEvent(void Function(dynamic event) f);
@JS()
external dynamic jsInvokeMethod(String method, String? params);
class FlutterPluginWeb {
static void registerWith(Registrar registrar) {
final MethodChannel channel = MethodChannel(
'flutter_plugin',
const StandardMethodCodec(),
registrar,
);
final pluginInstance = FlutterPluginWeb();
channel.setMethodCallHandler(pluginInstance.handleMethodCall);
//Sets the call from JavaScript handler
_jsOnEvent = allowInterop((dynamic event) {
//Process JavaScript call here
});
}
Future<dynamic> handleMethodCall(MethodCall call) async {
switch (call.method) {
case 'sendMethodMessage':
return sendMethodMessage(call.method, call.arguments);
default:
throw PlatformException(
code: 'Unimplemented',
details:
'flutter_plugin for web doesn\'t implement \'${call.method}\'',
);
}
}
Future<dynamic> sendMethodMessage(
String method, String? arguments) async {
final dynamic response =
await promiseToFuture(jsInvokeMethod(method, arguments));
//...
}
}
That’s all! Now you can add web support for existing plugins or create your own ones. Hope this article has been helpful to you.