LoginSignup
26
22

More than 3 years have passed since last update.

flutter web で firebase を使ったアプリを作成した知見

Last updated at Posted at 2019-12-07

flutter 界隈のみなさまこんにちは、師走はいかがお過ごしでしょうか。
この頃は急に寒くなり部屋からも布団からも出れない私はワイルドエリアでポケモンとキャンプをし愛犬ラクライを愛でる幸せなアウトドア生活を送っております。バッジ集めるのとかなんかどうでもよくなってきており、ただ野外でカレーを作る日々。ラクライはいいやつなので毎日だいたい同じカレーを食べさせても満面の笑みで頬張ります。まさかお母さんも送り出した娘がワイルドエリアで野生化し野宿と放浪を繰り返してるとは思っていないでしょう。ライバルのホップもいいやつなので今もきっとどこかの道端で主人公を待ちつづけているのでしょう。幻のポケモン?そんなイベントあったっけ?ポケモン図鑑…?ずいぶん昔に薪の足しにしたような?

…さて、本題に入ります。

今回、初 flutter, 初 flutter web を触ってみたので、その知見をまとめたいと思います。

概要

今回作ったアプリの大まかな構成は以下の通りです:

次の章から、それぞれに関する知見を、深ぼっていきたいと思います。

デプロイ周り

今回は firebase hosting へ Github Actions を用いてアプリをデプロイしました。
subosito/flutter-action が便利で、PRごとに flutter analyze で静的解析にかけたり、master に入ったコードを各環境用に flutter build を実行したりできます。
flutter build の結果を w9jds/firebase-action を使って firebase hosting にあげれば、誰でも簡単無料で flutter web 製のサイトを公開することができます。

ルーティング

今回は fluro を使ってルーティングを行いました。
パスパラメータ、クエリパラメータに対応しているため、ブラウザの location とも相性が良いです。
そのままでは web 用のビルドに含められないため、フォークして使用しました。

(web ビルドで依存が認められていない、 dart:io を取り除いたバージョン)
https://github.com/theyakka/fluro/pull/132

import 'package:fluro/fluro.dart' as f;

// メッセージ詳細
router.define(
'/rooms/:room_id//messages/:message_id',
handler: f.Handler(
  handlerFunc: (BuildContext context, Map<String, dynamic> params) =>
        LoginRequiredContainer(
      child: MessageDetailScreen(
        roomID: params['room_id'][0],
        messageID: params['message_id'][0],
      ),
    ),
  ),
  transitionType: f.TransitionType.material,
);

こんな形でルーティングの定義ができます。

ホットリロード

flutter の最大の旨味じゃないかなと感じるホットリロードですが、Web は現時点でホットリロードが効かないです。(ホットリスタートします)
開発は基本 Android か iOS で行った方がストレスが少ないです。

Does hot reload work with a web app?
No.

firebase

flutter web の firebase 対応は罠が多いです。
まず、flutter には iOS/Android/Web でユニバーサルな firebase ライブラリが現時点で存在しません。

flutter で firebase を利用する方法を一つ一つ見ていきましょう。

FlutterFire

FlutterFire という iOS/Android 対応の公式プラグインがあります。これが web 対応してくれれば万々歳なのですが、現時点で対応はしていません。

(ただ、執筆時点ですごい勢いで web 関連のコードが追加されていっているため、そのうち firestore なども web 対応されそうです)
https://github.com/FirebaseExtended/flutterfire/pull/1555

ひとまず、様子見が必要です。代替策を考えねばなりません。

firebase

Web 向けの firebase ラッパーです。JS 版の firebase を interop を駆使して Dart でラップしたものです。
flutter web で firebase を扱うならこれが一番ですが、如何せん Web 用なので Android/iOS ではビルドが通りません。辛い。

crossfire

上の二つのライブラリから iOS/Android, Web 間で共通のAPIだけ抜き出し、再定義した crossfire というライブラリがあります。
これを使えば Android/iOS/Web みんなで共通のコードベースを共有できる…と思いきや、対応APIがめちゃ少ないです。

auth に限ってはカスタムトークンのみの対応。(自前で認証作る人向けですね)

(AndroidX に対応したのが crossfire_flutter 3.0.0 からなのですが、依存が crossfire 2.0.0 なので、誤って crossfire 2.1.0 入れると痛い目見ます)

というわけで、コードベース共有は諦めました。適切に抽象化して各プラットフォームで実装するしかないです。

プロジェクトを分ける

firebase などの Web 依存(package:jspackage:html など)が pubspec.yaml に含まれていると、iOS/Android ビルドがそもそも通りません。
逆に dart:io などの Web からの依存が禁止されている依存を持っている場合は、Web のビルドが通りません。

そこで、Web, Native でプロジェクトを分けます。

ディレクトリ構成

以下のように、webmobile でそれぞれ flutter create します。
共通で使用する Widget, Screen や BLoC などは common というプロジェクト配下に置いて行きます。
(commonflutter create --template=package で作成します)

.
├── analysis_options.yaml
├── common
│   ├── lib
│   │   ├── blocs
│   │   │   ├── auth_bloc.dart
│   │   │   └── hoge_hoge_screen_bloc.dart
│   │   ├── entities
│   │   ├── repositories
│   │   │   ├── auth.dart
│   │   │   ├── user.dart
│   │   │   └── repositories.dart
│   │   ├── run.dart
│   │   └── views
│   │       ├── boundaries
│   │       ├── screens
│   │       │   └── hoge_huga_screen.dart
│   │       └── widgets
│   ├── pubspec.lock
│   └── pubspec.yaml
├── native
│   ├── android
│   ├── ios
│   ├── lib
│   │   └── main.dart
│   │   └── repositories
│   │       ├── auth.dart
│   │       ├── user.dart
│   │       └── repositories.dart
│   ├── pubspec.lock
│   └── pubspec.yaml
└── web
    ├── lib
    │   ├── main.dart
    │   └── repositories
    │       ├── auth.dart
    │       ├── user.dart
    │       └── repositories.dart
    ├── pubspec.lock
    ├── pubspec.yaml
    └── web
        └── index.html

そして、repository を abstract class で定義し、各プラットフォーム用のプロジェクトで実装することで、 pubspec.yaml の競合問題を解決します。

common/lib/repositories/auth.dart

import 'dart:async';

class Credential {
  final String userID;
  Credential(this.userID);
}

abstract class AuthRepository {
  Future<Credential> fetch();
}

実装側

web/lib/repositories/auth.dart
import 'dart:async';
import 'package:firebase/firebase.dart' as fb;
import 'package:common/repositories/AuthRepository.dart';

class AuthRepositoryImpl implements AuthRepository {
  AuthRepositoryImpl(this.auth) : super() {
    auth.onAuthStateChanged.listen((user) {
      if (user == null) {
        return;
      }
      _currentUser.complete(Credential(user.uid));
    });
  }

  final fb.Auth auth;
  final Completer<Credential> _currentUser = Completer<Credential>();

  Future<Credential> fetch() => _currentUser.future;
}

そして、各プラットフォームの main.dart で適切に DI を行えば、晴れて iOS/Android/Web 対応の firebase を使ったアプリを作成できました。

細かな気づき

その他、細かな flutter web の気づいた点を書き記しておきたいと思います。

role="button" aria-label="Enable accessibility" の見えないボタンが存在し、押下すると Semantic 系のノードが DOM に追加されアクセシビリティが担保される

ちなみに一度UIをマウスで操作するとボタンが消えるっぽい?

スクリーンショット 2019-12-07 1.34.00.png

VS Codeが便利

普段は vim を使っていますが、coc-flutter がバギーなので flutter 開発するなら VS Code が安定そう

以上

ここ数ヶ月の flutter web に関する知見でした。私はワイルドエリアに帰ろうと思います。ごきげんよう。

26
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
22