Dart/Flutter連載の3本目はFlutter Webを紹介します。
Flutter 2になって、Web向けに出力する機能もStableになりました。
Flutter for Webは標準のHTMLにするHTMLレンダラーと、CanvasKitレンダラーと2種類あります。後者はSkiaという2DグラフィックスのライブラリをWebAssembly化したものを使います。Skiaはウェブ向けではないFlutterでも使っているため、モバイルとの互換性の高さが期待されます。
現状では明示的に指定しなければauto(モバイルはHTMLレンダラー、PCはCanvasKitレンダラー)になりますが、明示的に指定もできます。これらの違いはまた後で触れますが、せっかくウェブが出せるようになったので、ウェブフロントエンドをFlutterで作ってみるための色々調査をしてみました。React/Vue/Angularを一通り業務で使ってみましたし、フロントエンド開発周りもここ5-6年ぐらい、書き方が違うぐらいでやっていることはあんまり変わらなくて個人的に飽きてきたこともあります。
ウェブアプリといえばRouter
SPAで管理画面を作っていく上で、最低限必要なことはRouterと呼ばれる機能です。VueやAngularだと標準で用意されています。Reactは標準はないですが、使うときはだいたい何かしら入れるでしょう。
FlutterはデフォルトでNavigotorというクラスがあります。以下のページがめちゃくちゃまとまっていますので、詳細はこちらをご覧ください。
https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade
ウェブアプリケーションユーザー目線で、いくつか知っておくべきポイントがあります。
- 1.0と2.0と大きく2種類に分かれる(ここでは2を扱います)ので、ウェブを検索して出てきた内容を参考にするには利用バージョンと同じかどうか注意が必要
- サンプルの一番シンプルな書き方だと、URLのパスを決めるのではなく、その場でウィジェットを上書きする(pushする)モードで、ウェブのよくある挙動とは違う動きになる
- named navigator routesという、ウェブのRouterに近い、パスのルールとその時の表示するウィジェットのマッピングを定義するモードもある(ネストもできる)
- named navigator routesでデフォルトはハッシュを挟んだパスになる(AngularでいうところのHashLocationStrategy)が、PathLocationStrategyも設定可能
- パスの一部をパラメータとして利用しようとすると面倒
あとは次のあたりも僕がFlutterを学び始めたときにちょっと悩んだポイントです。
- statefulとstatelessでウィジェットを作り分ける必要がある
- buildメソッドはReactのrender
- builderという言葉はVueのslot的な、特定のライフサイクルで呼ばれてビューの一部を返す何か←某握力王の人に教えてもらいました
- debug()関数でconsole.logに出力できる
最小のRouter
次のコードが↑に書いてあるnamed navigator routesを使った最小のコードです。2つの画面の間の遷移をします。まず、ルートのMaterialAppに、routesの引数でURLとページのマップを定義します。あとは、Navigatorクラスを使って、pushNamed()メソッドや、pop()メソッドを使ってページ遷移ができます。よくあるSPAと変わらないですね。
import 'package:flutter/material.dart'; |
こちらができあがりです。Android Studioで作った環境でウェブで表示してみたものになります。
なお、URLの一部がエンティティのIDとしてパスパラメータとして使いたい場合は、RouteInformationParserを継承したクラスを作ってアプリに渡す必要があります。上記のmediumのページの中でRouteInformationParserで検索して見てみれば書き方がわかりますが、面倒です。ここはそのうち改善されるのでは、ということを期待しています。
ハッシュがURLに入ってしまうのをやめる
PathLocationStrategy相当への切り替え方法については次のページで説明されています。
https://flutter.dev/docs/development/ui/navigation/url-strategies
まず、依存パッケージにflutter_web_pluginsを追加します。
dependencies: |
次に、main関数の中で、URLのルールを変更します。↑のページには、Web向けとそれ以外向けでルールを切り替える方法も紹介されていますが、ここではウェブでしか使わない前提でシンプルにmainに書いてしまっています。
import 'package:flutter_web_plugins/flutter_web_plugins.dart'; |
これでパスにハッシュが入ることがなくなりました。
Goのアプリケーションに組み込む
Goで作ったサーバーの管理画面をFlutterで作る前提で、go:embedでアプリにバンドルしてみます。以前、本技術ブログでVueで行ったことをFlutterでもやってみます。
https://future-architect.github.io/articles/20210408/
まずビルドします。CanvasKitのほうが描画性能は高いとのことですが、たぶん、レンダラーはHTMLが良いかと思います。
flutter build web --web-renderer=html --source-maps |
ビルドオプションには–releaseをつけることができます。つけるとビルドは遅くなります(M1 MacBook Proで20秒ほど。つけないと0.3秒)。
ビルド結果はbuild/web
フォルダに出力されます。
一見、CanvasKitもHTMLもファイルサイズがほとんど変わらない(3.4MBと3.5MB)のですが、CanvasKitでビルドすると、CanvasKitの本体のwasmのビルド済みのファイルをネット越しにダウンロードしているようです。これが2MBぐらいあるみたいですし、もしかしたらプロキシが必要なイントラネットで利用とか考えると、外部依存はないに越したことはありません。
14151:$2:function(a,b){return"https://unpkg.com/canvaskit-wasm@0.25.1/bin/"+a}, |
Goのファイルをいくつか作成します。go:embedが、今いるフォルダよりも子供のフォルダしか読み込めないので、Flutterのルートのフォルダでgo mod init flutter_with_goを叩いて、go.modを作成します。
ファイルを参照するgo:embedは次のように書きます。
package flutter_with_go |
NotFoundHandlerハンドラーは前回の記事のファイルの配信のハンドラーで紹介したコードとほぼ同じです。ファイルの置き場をプロジェクトルートにしてみたのと、パスがbuild/webになったぐらいです。main関数もほぼ以前と同じです。
無事、GoでもFlutter Webのビルド結果をホストできました。
今回のフォルダ構成は次の通りです。Goのコードはserverみたいなサブパッケージを作って入れてもよかったかも。
├── README.md |
サーバーへのHTTPアクセス
静的HTMLを表示するだけでは管理画面にはなりませんので、HTTPアクセスを行ってみます。より高度なサービスになると、昨日のエントリーのSwaggerを使ったサーバーアクセスや、GraphQLやgRPCを使いたくなるかもしれません。今時なプロトコルはどれでも利用できるのも、Flutterの良いところですが、今回はシンプルなHTTPアクセスをします。
題材としては今話題沸騰のイケてるWeb APIであるケンオールにアクセスしてみます。
ケンオールはアカウント登録するとAPIキーが発行され、これを使ってアクセスします。サンプルと言えど、APIキーはフロントエンドに置きたくないので、サーバー側で中継することとします。
サーバー側の実装
/api/postal/{code}
にアクセスしたら、住所情報を返すAPIをGoで実装しました。APIキーは環境変数で渡します。Vue.jsのときのサンプルの差分だけ表示します。
|
ビルドしたら試しにcurlでこのサーバーAPIを叩いてみます。バッチリですね(長いのでレスポンスは短くしてます)。
% curl http://localhost:8000/api/postal/1410032 |
フロント側の実装
フロント側からはサーバーアクセスをさせたいと思います。状態をもつのでstatefulなウィジェットとします。
class MyApp extends StatelessWidget { |
実サーバーアクセスと表示を行う部分はこちらです。フィールドの入力が7文字になったらサーバーアクセスを行い、取得してきた情報をStateに入れています。
class _KenAllState extends State<KenAll> { |
このHTTPアクセスには外部パッケージが必要なため、pubspec.yamlとHTTPリクエストを送っているコードへのimportの追加を行いま。
dependencies: |
import 'package:http/http.dart' as http; |
無事動いたようです。
まとめ
そろそろReact/Vue/Angularに飽きてきたかも? な人の新たなおもちゃとしてFlutter Webの紹介をしました。機能的には以下の3つを紹介しました
- Router周り
- ビルドした成果物がどうなっていて他の言語(Go)のサーバーにどう組み込めばいいのか
- サーバーへのHTTPアクセス
モバイルアプリ開発案件じゃなくてもFlutterができてしまうので、スカンクワークスにぴったりですね。用途が広くていつの間にかシェアを広げていた黎明期のGoと同じように、上司に内緒でこっそり導入に最適です。
Dart/Flutter連載の3記事目でした。次回は鶴巻さんのFlutterレイアウト入門です。