Flutter for WebのプロジェクトをGitHub ActionsでFirebase Hostingにデプロイする
タイトルのまんまです。
FlutterとFirebaseのバージョンは次のとおりです。
$ flutter --version Flutter 1.10.14 • channel dev • https://github.com/flutter/flutter.git Framework • revision 1946fc4da0 (12 days ago) • 2019-10-07 15:23:31 -0700 Engine • revision 1d62160fdb Tools • Dart 2.6.0 (build 2.6.0-dev.1.0 d6c6d12ebf) $ firebase --version 7.0.1
Workflow
次のワークフローで基本的には動きます。ただし、一部実装依存の部分があるので後述します。
- .github/workflow/main.yml
name: CI on: push: branches: - master jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Build uses: subosito/flutter-action@v1 with: channel: 'dev' - run: | flutter config --enable-web flutter pub get cat <<EOF > assets/secret.json ${{ secrets.CREDENTIAL_JSON }} EOF flutter build web - name: Archive Production Artifact uses: actions/upload-artifact@master with: name: web path: build/web deploy: name: Deploy needs: build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Download Artifact uses: actions/download-artifact@master with: name: web path: build/web - name: Deploy to Firebase uses: w9jds/firebase-action@master with: args: deploy --only hosting env: PROJECT_ID: ${{ secrets.PROJECT_ID }} FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
解説していきます。
Jobの流れ
BuildとDeployで分けてます。
Build
flutterコマンドを落としてきて、ビルドしたものをartifactに登録します。
GitHub Actionsでflutterを使うためのActionがいくつか用意されていたのですが、Dockerの方を使うとartifactのpathを調整する必要があったため、JavaScriptの方を選択しました。
Deploy
firebaseコマンドが使えるDockerコンテナを用意し、その中でartifactを落としてきて、Hostingにデプロイします。
こちらもActionが複数あったのですが、最もStarを集めてて使いやすそうなものを選びました。
Hosting以外のFirebaseの機能を利用している場合
今回の例にあたります。Firebase Config Objectを生成して、Firebase初期化時の引数に渡す必要があります。
この情報はpublicにしたくないものも含まれれるため、リポジトリには含めません。
今回のケースだと、 assets/secret.json
というファイルに情報を詰めています。このファイル名もファイルパスも、渡し方も決まりはないので、プロジェクトごとに異なると思います。
このプロジェクトでは次のように初期化しています。
final secret = await rootBundle.loadStructuredData<Secret>("assets/secret.json", (jsonStr) async { final secret = Secret.fromJson(json.decode(jsonStr)); return secret; }); initializeApp( apiKey: secret.apiKey, authDomain: secret.authDomain, databaseURL: secret.databaseURL, projectId: secret.projectId, storageBucket: secret.storageBucket, );
秘匿情報の扱い方は以前まとめたものが参考になると思います。
まとめ
Flutter for Webのプロジェクトをデプロイするだけなら簡単です。基本的にBuildのjobで必要なものは揃っているので、あとはどこにホスティングするにしても同じ感じで行けると思います。
一部実装依存になる部分が出てきたら調整が必要です。
今回作成したページはこちらです。
参考
GitHub ActionsでFlutterのPluginをリリースできるActionをMarketplaceに公開した
FlutterでPluginを作りたいと思ったらPubに登録するのが一般的です。
もともとGitHub Actionsでリリースできるようにしていたのですが、Pluginに依らずやることは同じなのでAction化してMarketplaceに公開しました。
Marketplaceに公開までにあたりいくつかハマった点があったので共有します。
Dockerイメージのユーザはrootである必要
今回はcirrues/flutterのDockerイメージを利用していたのですが、ユーザを指定していたため、Permission deniedで怒られました。
現状GitHub ActionsはDockerのデフォルトユーザ(root)での実行を想定しています。それ以外だとGITHUB_WORKSPACE
にアクセスできないため、ファイル作成などでPermission errorが出ます。
なので、今回のケースにおいてはDockerfile内でユーザをrootに指定しなおします。
- Dockerfile
USER root
Dockerイメージをそのまま使う場合は引数で指定してあげるとうまくいきます。
container: image: cirrusci/flutter:stable options: -u root
inputsは自動的に環境変数に登録される
env
で指定しなくても環境変数として渡ります。もしも明示的に引数として扱いたい場合は args
に渡してあげればよいです。
inputsで渡した場合は頭に INPUT_
が付いて大文字に変換された名前で環境変数に登録されます。任意の名前で扱いたい場合は env
で渡すのもよいと思います。
同一リポジトリでの確認
作成しているGitHub Actionsの確認に利用できます。.github/workflows/
に置いたyamlが、通常のGitHub Actionsと同様に実行されます。 uses: ./
と指定してあげることで、作成中のActionを指定することができます。自分が確認している範囲では、ローカル環境での動作確認はまだできないみたいです。
jobs: test_job: runs-on: ubuntu-latest name: Test steps: - name: Checkout uses: actions/checkout@v1 - name: Test action uses: ./
作成したActionのMarketplaceでのURLはname
が使われる
GitHubのリポジトリ名が使われると思ってたのですがそうではなかったです。作成の際にはnameに気をつけてください。
ややこしいことに、yamlのAction名にはリポジトリ名が利用されます。揃えておいたほうが利用者にとってわかりやすいので、なるべく揃えたほうが良いと思います。
Actionを利用するときのバージョン指定は完全一致じゃないとNot foundになる
よく使う、 actions/checkout@v1
のようにメジャーバージョンだけ指定すればよしなにv1.y.z
の最新のバージョンが利用されると思っていたのですがそうではありませんでした。
現状はタグ名の完全一致で見ているようです。 v1
タグがしっかり存在していました。
しかしこの仕様は見直される可能性があるみたいです。
まとめ
ドキュメントをよく読むことに尽きるのですが、構造化されてなくて現状結構情報が探しにくいと思います。すでに公開されているActionが非常に参考になるので探して動かしてみると色々わかると思います。
また、Community Forumに投稿したり検索したりするとわりと情報が見つかるので活用すると良いです。
こちらは今回公開したActionの利用例です。
参考
Virtual environments for GitHub Actions - GitHub Help
Creating a Docker container action - GitHub Help
Solved: Version numbering for Actions - GitHub Community Forum
パッケージが統合されたFlutter for Webへのアップグレード
Flutter for Webのパッケージがflutter
に統合されました。それに伴い既存のFlutter for Webのプロジェクトでアップグレードが必要になったのでその記録です。
Upgrading
まだstableには来てないのでdevに向けて、webを有効にします。
$ flutter channel dev
$ flutter upgrade
$ flutter config --enable-web
channelの変更は、次のバージョンがstableに来たら不要になると思います
$ flutter --version Flutter 1.10.6 • channel dev • https://github.com/flutter/flutter.git Framework • revision cc3ca9a916 (5 days ago) • 2019-09-25 10:57:58 -0400 Engine • revision 63949eb0fd Tools • Dart 2.6.0 (build 2.6.0-dev.0.0 69b5681546)
既存プロジェクトですでに作成済みの web/main.dart
を削除します。
次に、プロジェクトをFlutter SDK向けに変更していきます。
$ flutter create .
pubspec.yaml
を置き換えて、flutter_web
の部分をflutterに置き換えます。
web/assets/
に置いていた FontManifest.json
の内容を移行します。
MaterialIconsは
flutter: uses-material-design: true
となり、
fontを利用していた場合は fonts
フォルダをプロジェクトルートに作り、そこにフォントファイルを置きます。その後、font名の指定とfontのpathを書きます。
flutter: ... fonts: - family: KosugiMaru fonts: - asset: fonts/KosugiMaru-Regular.ttf
画像などの素材は、 assets
フォルダをプロジェクトルートに作り、そこに移動させます。その後フォルダを指定します。
flutter: ... assets: - assets/
これで web/assets
フォルダは空になったと思うので、削除します。
実行は次のコマンドです。Windowが立ち上がります。
$ flutter run -d chrome
ドキュメント的には以上なのですが、自分の環境では更に次のような手順が必要でした。
追加手順
rootBundleのimport
rootBundle
を使っている場合、含まれるパッケージが変わっているので flutter/service
からimportします。
import 'package:flutter/services.dart' show rootBundle;
assetsのパス
rootBundle
でassetsにアクセスしている場合、pathが次のように変わります。
some.json
だったものが
assets/some.json
になります。assetsに指定しているフォルダからpathを指定する必要があります。
BinaryMessengerへのアクセス
runApp
より早いタイミングで rootBundle
を利用している場合、chromeのconsoleにエラーが出ます。
Uncaught (in promise) Error: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. If you're running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first. If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding.
rootBundle
がbinaryMessengerを利用しているため、怒られます。回避するためにはエラー文の通り、
WidgetsFlutterBinding.ensureInitialized()
をrootBundle
より先に呼び出します。
dart:htmlのimportエラー
自分はIntelliJで開発しているのですが、Target of URI doesn’t exist: ‘dart:html’.
のエラーが出ました。ですが、ターミナルからビルドする分には問題ありませんでした。
現状Dart SDKが提供しているもので、利用できるものとそうでないものがあるみたいです。
Release Buildのアウトプット先
今までは build/
だったのですが、build/web/
に変わりました。Deployする際には注意が必要です。
リリースビルドのコマンドは次です。デフォルトでreleaseのconfigurationになっています。
$ flutter build web
まとめ
パッケージ統合以前のFlutter for Webのプロジェクトでも、アップグレードガイドがあるので移行自体はさっとできます。
ただし、いくつか注意点があるのでハマりすぎないように気をつけてください。
自分の環境において、マイグレーションのドキュメントに書いてないことで気をつける必要があったことは次の5点です。
- rootBundleのimportが変わる
- assetsのパスが変わる
- assetsに早いタイミングでアクセスしている場合は
WidgetsFlutterBinding.ensureInitialized()
が必要になる - Dart SDKのいくつかのパッケージはWebでの利用に制限がある
- アウトプットのpathが異なる
参考
Upgrading from package:flutter_web to the Flutter SDK · flutter/flutter Wiki · GitHub
Properly enable dart:html imports · Issue #35588 · flutter/flutter · GitHub