🎃

【Flutter Web】Javascriptを呼び出す (packages:js)

2022/10/20に公開

webの開発の為に多種多様なjavascriptのライブラリが存在しますが、flutter web開発時に使いたくてもdart・flutterでそれらを扱うパッケージが存在しないこともあると思います

そんな場合にはdart側からJavascirptの処理を直接呼び出すサポートをしてくれるのがjsパッケージです

https://pub.dev/packages/js

その基本的な使い方を見ていきます

前提

今回は「 Flutter webにて外部のjsライブラリもしくは自身で作成したjsの処理を実行する 」事を想定しており、純粋にDartからjs処理を呼び出す事についてはあまり言及しません

概要

全体の流れとしては、以下の通りとなります

  1. webフォルダ配下にjavascriptの処理を定義したファイルを作成
  2. web/index.html内でそのファイルをインポート
  3. Dart(Flutter)側でインポートしたjsファイルの処理を呼び出すメソッドを定義

ファイル構成

.
├── lib
│   ├── my_javascript.dart # このファイルにjs処理を書いていく
│   └── main.dart
│
└── web
    ├── my_javascript.js # このファイルにjs処理を書いていく
    ├── index.html
    └── manifest.json

基本の流れ

  1. jsパッケージをインポートしておく
$ flutter pub add js
  1. jsのfunctionを定義したファイルをwebディレクトリに作成
web/my_javascript.js
function basicFunction() {
    console.log("function executed!")
}
  1. web/index.htmlにて定義したjsファイルをインポート
web/index.html
  <script src="my_javascript.js"></script>
  1. js処理を呼び出すDart側のメソッドを定義
lib/js/my_javascript.dart
()
library my_javascript;

import 'package:js/js.dart';

('basicFunction')
external void basicFunction();
  • 前提としてjsファイルの呼び出し部分の前には@JS()アノテーションを付ける
  • libraryとしてjsの処理を定義したファイルを指定
  • 定義したjsの処理を割り当てるdartメソッドの前に@JS(<割り当てるfunction名>)を付ける
  • dartメソッドの頭にexternal句を付けて引数、返り値をjsの処理と同様にする
  1. Dart側のメソッドを実行する
    あとはDart側で定義したメソッドを実行するだけです
lib/main.dart
onPressed: ()=> basicFunction(),

外部jsライブラリの使い方

上記では自分で定義したjavascriptの処理を呼び出しましたが、今度は公開されている外部のjsライブラリを使ってみましょう

扱い方は2通り

外部ライブラリを使う場合は、「自分で定義したjavascriptの処理で使うやり方」と「dart側で直接外部ライブラリの処理を呼び出すやり方」と2通りが存在します

自分で定義したjsの処理で使う

  1. 外部ライブラリをweb/index.htmlでインポートしておく
web/index.html
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
  1. 自分で定義したjavascriptの処理内で呼び出す
    web/index.htmlでインポートしておけば、自身のjsファイル内で改めてimportする事なく外部ライブラリの処理を呼ぶ事ができます
web/my_javascript.js
// function using library
function functionUsingLibrary(list){
    // using max method from lodash
    console.log(`biggest number is ${_.max(list)}`)
}

Dart側から直接呼び出す

  1. 外部ライブラリをweb/index.htmlでインポートしておく
web/index.html
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
  1. Dart側で外部ライブラリの処理を直接呼ぶ
    Dart側で直接呼び出す際はlibraryとしてインポートします
lib/js/lodash.dart
('_')
library lodash;

import 'package:js/js.dart';

('uniq')
external List<int> uniq(List<int> list);

どちらが良いのか?

呼び出したいjsの処理がそのライブラリの処理だけであれば、Dart側から直接呼んでも良いですが、複数あったり、今後増える可能性もあるのであれば 自分で定義したjsファイルを用意し、そこに処理を集約させる 方が個人的には分かりやすく、責務もスッキリすると考えています。あくまでも個人的な意見ですが。

jsの処理に引数を渡す

引数を受け取るjs処理があると思います。Stringboolなどプリミティブな型はそのまま渡せますが、オブジェクトを引数として渡す場合は、そのオブジェクトのクラスをjsパッケージを使って定義する必要があります

lib/js/my_javascript.dart
('functionWithObjectArg')
external void functionWithObjectArg(Person person);

('')

class Person {
  external String get name;
  external int get age;
  external String get message;
  external factory Person({String name, int age, String message});
  • jsに用意されている@anonymousアノテーションを付ける
  • @anonymousアノテーションが付いたクラスではfactoryコンストラクタを定義する必要がある
  • 通常、名前付き引数は使えないので、上記の通りクラスとして定義し、そのコンストラクタを名前付きにする事で実現させる
lib/main.dart
  onPressed: () => functionWithObjectArg(
    Person(
      name: 'John',
      age: 52,
      message: "Hi, I'm John!!",
    ),
  ),

jsの処理に関数を引数として渡す

関数を引数で渡したい場合は、渡す関数をallowInteropメソッドでラップする。onSuccessやonFailureのコールバックメソッドを使う際に有効です。

web/my_javascript.js
// example with passing function
function functionWithFunctionArg(func) {
    const result = func()
    console.log(`function result was ${result}`)
}
lib/js/my_javascript.dart
('functionWithFunctionArg')
external void functionWithFunctionArg(Function func);
lib/main.dart
  onPressed: () {
    functionWithFunctionArg(allowInterop(() {
      return 125 + 25;
    }));
  },

jsのPromiseをdart側で実行する

Promiseのjs処理を呼びたい場合、Dart側でそのままFuture型に変換する事はできません。

以下二つの対応が必要です

  1. js_utilライブラリ(jsパッケージに内包されている)のpromiseToFuture関数でFutureに変換する
  2. promiseToFutureメソッドは引数としてObjectを受け取る為、dart側のインターフェースでは Objectを返すメソッドとして定義する必要がある
web/my_javascript.js
// example with Promise function
async function functionWithPromise() {
    console.log("execution started")
    await new Promise(resolve => {
        setTimeout(resolve, 3000) // wait 3 seconds
      });
      console.log("call waited 3 seconds")
}
lib/js/my_javascript.dart
('functionWithPromise')
external Object functionWithPromise();
lib/main.dart
// 呼び出し
  onPressed: () async {
    await promiseToFuture(functionWithPromise());
    print('execution finished');
  },

おわり

コード:
https://github.com/heyhey1028/flutter_samples/tree/main/samples/js_sample

以上の知識があれば、大体のjsライブラリは活用する事が出来るかと思います。Web開発では豊富なjsライブラリを使いたいシーンも、そのライブラリに対応したFlutterパッケージが存在しない事も多々有ります。packages:jsはそんな制約を一気に取っ払ってくれる強力な助っ人です。

参考

https://flutter-square.com/js-call-dart/ (ja)
https://liewjuntung.medium.com/ use-javascript-in-flutter-web-a6eed3efb9a0 (en)
https://qiita.com/tfandkusu/items/b8b498d60ddff3db4d50 (ja)
https://codeburst.io/ how-to-use-javascript-libraries-in-your-dart-applications-e44668b8595d (en)
https://www.youtube.com/watch?v=zoN1_5tYzOM&t=44s (en)

Discussion