Open5

【Flutter Web】jsパッケージを使ったLine LIFF開発

heyhey1028heyhey1028

必要(そう)なツール

◆ ngrok

  • ローカルPC上で稼働してるネットワークを外部に公開してくれるサービス
  • ngrokを通して、localhostで立ち上げてるWebアプリケーションを別端末などからもアクセス可能にしてくれる
  • httpsのurlでも公開してくれるので、https接続でしか動かない機能の検証も可能
  • ただ公開してくれるだけでセキュアではないので、後述のmkcertを使って証明書を発行する事でセキュアな状態で公開する事が可能?

インストール方法

https://qiita.com/miriwo/items/8c1e6550a5ab279d60b5

◆ mkcert

参考

https://qiita.com/k_kind/items/b87777efa3d29dcc4467

heyhey1028heyhey1028

ローカル開発環境の整備

  1. 事前準備
  • テスト用のLINEグループを作る
  • そこに登録したLIFFアプリのLIFF URLを投稿しておく

LIFF URL
LINEグループ

  1. ローカルサーバーの立ち上げ
$ flutter run -d web-server --web-port=8080
  1. ngrokを使って、ローカルサーバーを外部公開
$ ngrok http 8080
  1. ngrokが吐き出すhttpsのurlをLIFFアプリのエンドポイントURLに登録
ngrok                                                                                               (Ctrl+C to quit)
                                                                                                                    
Join us in the ngrok community @ https://ngrok.com/slack                                                            
                                                                                                                    
Session Status                online                                                                                
Account                       heyhey1028 (Plan: Free)                                                             
Version                       3.0.7                                                                                 
Region                        Japan (jp)                                                                            
Latency                       -                                                                                     
Web Interface                 http://127.0.0.1:4040    
                               # ↓ このURL                                                             
Forwarding                    https://xxxx-xxx-xxx-xxx-xx.jp.ngrok.io -> http://localhost:8080            
                                                                                                              
Connections                   ttl     opn     rt1     rt5     p50     p90                                           
                              0       0       0.00    0.00    0.00    0.00                                          
                                                                                

  1. LINE内でLIFF URLにアクセスすると現在ローカルで立ち上げたFlutter webが表示される
heyhey1028heyhey1028

壁①:vscodeのlaunch.jsonでビルドしたローカルサーバーではdart debug extensionが必要になる

現象:

  • launch.jsonで下記の通り定義しデバックを実行
      {
        "name": "Debug web-server",
        "program": "lib/main.dart",
        "request": "launch",
        "type": "dart",
        "args": [
            "-d",
            "web-server",
            "--web-port=8080"
        ]
      },
  • localhost: 8080にアクセスするも下記のエラーが表示され、ずっとローディング状態となる

解決策:

  • コンソールからflutter run -d web-server --wep-port=8080を実行して立ち上げたローカルサーバーでは正常に起動
  • launch.jsonで立ち上げたローカルサーバーの場合、chrome側にdart debug extensionをインストールして実行すると正常に起動する dart debug extension

壁②:FlutterWebデプロイ時に.envが読み込まれない

  • LIFF SDKの使用にLIFF IDが必要となる
  • 秘匿したいのでflutter_dotenvを使用したい
  • しかしfirebase hostingへのデプロイ時、.envファイルの読み込みが行われない

原因

firebase.jsonignore項目に"**/.*"が含まれている為、ドットから始まるファイルが無視されてしまう

解決策

① ファイル名を変更する

  • ファイル名を.envではなく、envとして作成
  • dotenvの読み込みでenvファイルを読み込む
main.dart
Future<void> main() async {
 // .envファイルの読み込み
+  await dotenv.load(fileName: "env");
-  await dotenv.load(fileName: ".env");
 runApp(const MyApp());
}

② ignore項目を変更

  • .envファイルだけはignoreされない様、!**/.envを追記
firebase.json
{
  "hosting": {
    "public": "build/web",
    "ignore": [
      "firebase.json",
+      "**/.* !**/.env",
-       "**/.*",
      "**/node_modules/**"
    ]
  }
}

参考:

heyhey1028heyhey1028

jsパッケージを使用し、LIFF SDKを実行する

jsパッケージの基本

流れ

  1. jsのfunctionを定義したファイルをwebディレクトリに作成
web/audio.js
function playAudio(audioPath) {
    //htmlからAudioタグを取得
    var audioElement = document.getElementById("audio");
    //取得したAudioタグに対して、引数にて指定されたパスを設定
    audioElement.src = audioPath;
    //再生
    audioElement.play();
}
  1. web/index.htmlにて定義したjsファイルをimport
web/index.html
  <script src="audio.js"></script>
  1. 前提としてjsファイルの呼び出し部分の前に@JS()アノテーションを付ける
  2. libraryとしてjsの処理を定義したファイルを指定
  3. jsのfunctionを割り当てるdartのfunctionの前に@JS(<割り当てるfunction名>)を付ける
  4. jsのfunctionを割り当てるdartのfunctionの頭にexternal句を付けて引数、返り値をjsのfunctionと同様にする
lib/main.dart
() // 2
library audio; // 3
import 'package:js/js.dart'; 

//interface function
('playAudio') // 4
external void playAudio(String audioPath); // 5

外部のjsライブラリの使用

自前で用意したjsの関数ではなく、jsライブラリを使いたい場合は以下の流れで使用可能

  1. web/index.htmlにてライブラリをimport
web/index.html
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

ファイル構成

jsの処理にオブジェクト引数を渡す

  • 自前で定義したオブジェクトを引数として渡す場合はpackage:jsが用意している@anonymousアノテーションを付ける
  • @anonymousアノテーションが付いたクラスではfactoryコンストラクタを定義する必要がある
  • 通常、名前付き引数は使えないので、下記の通りクラスとして定義し、そのコンストラクタを名前付きにする事で実現させる

/// A class marked with [anonymous] must have an unnamed factory constructor
/// with no positional arguments, only named arguments. Invoking the constructor
/// desugars to creating a JavaScript object literal with name-value pairs
/// corresponding to the parameter names and values.

dog.dart
()
library dog;  

// The above two lines are required
import 'package:js/js.dart';

()
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); // 引数としてオブジェクトを渡す
}

()

class Options {
  external bool get bed;
  external String get hardness;
  external factory Options({bool bed, String hardness});
}

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

  • 上記のexternal void jump(Function(int height) func); の様に関数を渡したい場合は、渡す関数をallowInteropメソッドでラップする
main.dart
dog.jump(allowInterop((int height){
  print(height);
}));

jsのPromiseをdart側で実行する

  • JavascriptのPromiseを実行する場合は、js_utilライブラリ(jsパッケージに内包されている)のpromiseToFuture関数でFutureに変換する
  • promiseToFutureメソッドは引数としてObjectを受け取る為、dart側のインターフェースではObjectを返すメソッドとして定義する必要がある
app.js
async function classify(image) {
    const result = await iKutModel.classify(image);
    return JSON.stringify(result, null, 2);
}

main.dart
('classify')
external Object classify(ImageElement img);

// 呼び出し
String result = await promiseToFuture(classify(imageElement));

参考

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