Is there a hot JavaScript library that you want to use in Flutter Web but there is no equivalent for it in Dart? You are in luck! Dart was originally a language for internet browsers to begin with so they have this sweet Dart package called the js which you can use to interop between JavaScript and Dart.
To provide an example, we want to access this TypeScript class on the Flutter side. Let’s create a Dog.ts
file as shown below. We have
- A constructor
- Properties of
name
andage
of typestring
andnumber
respectively. - Getters and Setters for
name
andage
. - A
bark
method - A
jump
method that receives a function parameter that takes inheight
of typenumber
- A
sleep
method that takes in anobject
.
// Dog.tsclass Dog {
private _name: string;
private _age: number;constructor(name: string, age: number) {
this._name = name;
this._age = age;
}get name(): string {
return this._name;
}get age(): number {
return this._age;
}bark() {
console.log(`${this._name}:${this._age}:: Woof!`)
}jump(func: (height: number)=>void){
func(20)
}sleep(options: {bed: boolean, hardness: string}){
if (options.bed){
console.log(`${_name} is sleeping on a ${options.hardness} bed.`)
} else {
console.log(`${_name} is sleeping on the floor. :(`)
}
}
}
Afterwards, we’ll convert this TypeScript file into JavaScript file with
tsc --target ES5 Dog.ts
. With this done we’ll have a Dog.js
file alongside Dog.ts
.
Note that --target ES5
is required as according to the answer provided in the Stackoverflow post here:
“Normally JS interop would rely on function hoisting to ensure that an old-style JS class was in scope. ES6 classes however aren’t hoisted and aren’t available where Dart expects them to be. ”
We’ll leave this aside for now and we’ll create an empty Flutter project and import the js
library in the pubspec.yaml
like so:
dependencies:
flutter:
sdk: flutter
js: ^0.6.1
Note: Use Flutter Beta to enable the development of Flutter Web.
We’ll create an empty Dart file called Dog.dart
. Inside this Dog.dart
class, we’ll have our Dart <-> JS interop code.
@JS()
library dog;
// The above two lines are requiredimport 'package:js/js.dart';@JS()
class Dog {
external Dog(String name, int age);
external String get name;
external int get age;
external void bark();
external void jump(Function(int height) func);
external void sleep(Options options);}@JS()
@anonymous
class Options {
external bool get bed;
external String get hardness;
external factory Options({bool bed, String hardness});
}
the @JS()
annotation comes with the js
package which annotates our Dog
class in Dart to interop with the JavaScript’s Dog
class. The external
is put in front of the constructors
, methods
as well as the getter
and setters
.
If we want to have a custom name for the class in the Dart side. we can name it differently if we add a string as a parameter in the JS()
annotation like shown below, although it is discouraged according to the comments in the js package
.
@JS("Dog")
class DartDog {
...
}
JavaScript objects as a parameter
To pass a JavaScript object as a parameter. We’ll need to create a class and annotate it with @JS()
and @anonymous
. We will use the Options
class as the example shown below.
@JS()
@anonymous
class Options {
external bool get bed;
external String get hardness;
external factory Options({bool bed, String hardness});
}
And with that, we can pass the Options
class to the sleep
method like so
@JS()
class Dog {
...
external void sleep(Options options);
}
Passing Functions to JavaScript
For the function parameter, we’ll need to wrap our Function
with allowInterop
like this
dog.jump(allowInterop((int height){
print(height);
}));
Hooking things up
To make this work, copy the generated Dog.js
from the TypeScript file into the web
folder of the Flutter project alongside index.html
. Next, open up index.html
and add this line before the import main.dart.js
script tag.
<script src="Dog.js" type="application/javascript"></script>
A full example of the index.html
is shown below:
<!DOCTYPE html>
<html>
<head>
<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="ts_app">
<link rel="apple-touch-icon" href="icons/Icon-192.png"><!-- Favicon -->
<link rel="shortcut icon" type="image/png" href="favicon.png"/><title>ts_app</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>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script><!-- Add the import Dog.js script tag here here -->
<script src="Dog.js" type="application/javascript"></script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
We are nearly there, create a widget with a button in the centre of the screen which will use the Dog
class.
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter2JS"),),
body: Center(
child: RaisedButton(
onPressed: (){
var dog = new Dog("Bear", 12);
dog.bark();
print(dog.age);
print(dog.name);
dog.jump(allowInterop((int height){
print(height);
}));
dog.sleep(Options(bed: true, hardness: "Soft"));
},
),
),
);
}
}
After that, we can create and call our JavaScript Dog
class in Flutter Web!
We can see the Dog class is printing out in the console log.
Thank you for reading!