Flutter for Webですでに公開しているFlutterアプリをWebに対応させてみた


はじめに

この記事は、すでにFlutterで開発してiOS・Androidにて公開していることアプリを、Flutter for Webを使ってWeb対応させた時の経験や気付きなどをまとめたものです。
なお、Flutter for Webはまだbetaであり、製品として公開することは公式に推奨されていません。
ご承知の上でご覧ください。

Web対応させたアプリについて

概要

今回Webに対応させるアプリはcubookという個人開発のアプリです。
ボーイスカウトのカブスカウトという小学校3年生〜5年生の子供とその指導者向けの、カブブックと呼ばれる教科書の画像や動画を含めた進捗の共有や管理を行うアプリになります。
ホームページ
GitHub

アーキテクチャ

FlutterでWebアプリを動かす手順

チャンネルをベータにして、FlutterのWebを有効にします

$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web

有効になると、デバイス一覧にChromeが表示されるようになります

$ flutter devices
1 connected device:

Chrome (web) • chrome • web-javascript • Google Chrome 87.0.4280.88

プロジェクトのルートディレクトリで以下のコマンドを実行し、Web対応にします

$ flutter create .

Chrome上でアプリを実行します


$ flutter run -d chrome

Web対応のために修正したコード

プラグインなどによって、コードの書き方を変える必要などが出てきます。

Cloud Storage

Upload Taskの作成にはアプリとWebで処理を切り替える必要があるので要注意
以下がサンプルコードです

import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

//画像を取得する
final file = await ImagePicker().getImage(source: ImageSource.gallery, imageQuality: 50);

//保存するディレクトリとファイル名の指定
int timestamp = DateTime.now().millisecondsSinceEpoch;
String subDirectoryName = 'hogehoge';
final ref = FirebaseStorage.instance
    .ref()
    .child(subDirectoryName)
    .child('${timestamp}');

//Upload Taskの作成
UploadTask uploadTask;
if (kIsWeb) {
  uploadTask = ref.putData(await file.readAsBytes()); //Webでの処理
} else {
  uploadTask = ref.putFile(File(file.path)); //アプリでの処理
}

//アップロード
dynamic snapshot = await Future.value(uploadTask);

Cloud Functions

以下のように、httpリクエストでCloud Functionsを呼び出している場合、CORSエラーが発生してしまいます。

String url = "https://asia-northeast1-xxxx.cloudfunctions.net/somefunction";
Map<String, String> headers = {'content-type': 'application/json'};
String body = json.encode(<String, dynamic>{
  'hoge': 'hoge',
});
http.Response resp = await http.post(url, headers: headers, body: body);

関数をonRequestからonCallに変更してプラグインで呼び出すようにするのが一番手っ取り早いです。

HttpsCallable callable = FirebaseFunctions.instanceFor(region: 'asia-northeast1').httpsCallable('somefunction',options: HttpsCallableOptions(timeout: Duration(seconds: 5),));
final results = await callable(<String, String>{
  'hoge': 'hoge',
});
String resp = results.data;

ImagePicker

ImagePickerで放置していたMigrationの箇所でエラーが起きてしまっていたので、


File image = await ImagePicker.pickImage(source: ImageSource.gallery, imageQuality: 50);

から


final image = await ImagePicker().getImage(source: ImageSource.gallery, imageQuality: 50);

に変更

showLicensePage

package_infoがWebに対応していないため、showLicensePageを読み出す際にapplicationVersionの指定を工夫する必要があります。
今回は以下のように対応しました。


ListTile(
    leading: Icon(Icons.list),
    title: Text('ライセンスを表示'),
    onTap: () => kIsWeb
        ? showLicensePage(
            context: context,
            applicationName: 'cubook',
            applicationVersion: 'web',
            applicationLegalese: '©︎ 2020 山本虎太郎',
          )
        : showLicensePage(
              context: context,
              applicationName: 'cubook',
              applicationVersion: packageInfo.version,
              applicationLegalese: '©︎ 2020 山本虎太郎',
     ),
),

AdMob

公式とサードパーティのプラグインを使っていたが、全てWebに非対応。
駄菓子が買えるかくらいの収益しか入っていなかったのでこれを機に削除。(大赤字)

Firebase UI

アプリの認証では、Firebase UIというネイティブアプリ向けにGoogleが事前に用意したログインフローを適用してきました。Web向けにも用意はされていますが、Flutter for Webにはアプリ版のMethod Channelのような仕組みが無いため表示することが技術的に不可能です。そのため、Webでのログイン画面は自分で新たに作成しました。(Googleさん、Flutter用のFirebase UI作ってくれるとすごーく嬉しいです)

Webに対応していなかったプラグイン(執筆時点)

AdMob

このアプリの場合は機能を消してしまいました

Firebase messaging

現状Web版で通知は送れないです

Firebase Clashlytics

現状Web版でクラッシュ情報の収集はできません

open_file

データをエクセルに出力したファイルを開くために使っていました。
pub.devを見るとWebに対応しているような記述があるのですが、このアプリでは動作していません。

雑感

当初Web対応には2・3週間はかかることを覚悟していましたが、着手してから一週間も経たずにある程度動くところまでできたのは、さすがFlutterと感じました。最初にも触れた通りFlutter for Webはまだbetaですが、公式プラグインを中心に段々と対応してきています。パフォーマンスにおいては普通のWebアプリやアプリとして動かしたFlutterアプリより劣ってはいます。とはいえ、まだbetaであることや、何より大部分を共通のコードで動かせているので、今後より改善されると期待しています。

参照

Building a web application with Flutter
pub.dev