Flutterで手軽にPWA ~2020年末の対応状況


この記事は、
softdeviceのみんなでゆるく記事を書く。 Advent Calendar 2020
の12/17の記事として公開しています。

はじめに

最新のFlutterWebでは、標準でPWAに対応しました。

Flutterはモバイルアプリ用フレームワークなので PWAと相性が良さそうです。しかも、どちらもGoogleが推進している技術なので、今後にも期待できます。
個人的には、1つのコードで、Web(PWA)、Android、iOS、デスクトップアプリを開発できることは、フレームワーク選定において大きなアドバンテージになるので、気になっていました。

ということで、FlutterWeb標準でどういう機能が追加されたのか、触りながらみていきます。

PWAとは

Googleなどが提唱している概念で、Webサイトをあたかもネイティブアプリのように利用できる技術です。PWAに対応することで、ネイティブアプリのようなUXを提供することができます。
詳しい定義については、下記の解説をご覧ください。

プログレッシブウェブアプリの紹介 -MDN web docs
What makes a good Progressive Web App? -Google web.dev

MDNとGoogleで少し定義が違いますが、ホーム画面に追加オフラインキャッシュプッシュ通知 の3つが提供されれば、PWAと言って良さそうです。

FlutterでPWAを試すには

PWA対応で必要な手順はそんなに多くありません。
しかも、ほとんどFlutterが自動でやってくれます。

PWA対応手順

  1. WebサイトのHTTPS化
  2. ホーム画面に表示するアイコン作成・設置
  3. WebアプリManifest(manifest.json)の作成・設置
  4. Service Workerの作成・設置

1 はHTTPS化されているホスティングサービス Firebase Hosting を利用すると手軽です。
2〜4 は、少し前まで手動で行う必要があったのですが、現在は要らなくなりました。
Flutterをビルドすると自動で作成・設置されます。

ということで、こちらの手順を進めると、PWAを試すことができます。
【FlutterWeb】Firebase Hosting でWebアプリを公開する - Qiita

PWAを触ってみる

公開したWebアプリを開いてホーム画面に追加すると、アプリアイコンが設定されて全画面表示で起動しました。
起動時の読み込みも早く、オフラインにしても動作します。
さらに、MacのChromeでも、Webアプリとして追加できました。

▼Android

▼iOS

▼Mac Chrome

簡単に解説

FlutterWebを有効にすると、Webフォルダが追加されます。
このフォルダがビルドのテンプレートとなります。

iconsフォルダ

ホーム画面に追加した時のアイコンです。

manifest.json

ホーム画面に追加するための設定ファイル。スプラッシュ画面にも反映されます。
現時点では、AndroidとChrome用です。

manifest.json
{
    "name": "flutter_web",
    "short_name": "flutter_web",
    "start_url": ".",
    "display": "standalone",
    "background_color": "#0175C2",
    "theme_color": "#0175C2",
    "description": "A new Flutter application.",
    "orientation": "portrait-primary",
    "prefer_related_applications": false,
    "icons": [
        {
            "src": "icons/Icon-192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "icons/Icon-512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}

manifestの変更は、App Manifest Generatorというサイトが便利です。
アイコンのサイズ変換とmanifest.jsonの生成が簡単にできます。

各要素については、下記のリンクが参考になります。
Web App Manifest - MDN web docs
Add a web app manifest - Google web.dev

index.html

manifest.jsonのリンク、Service Workerの設定が追加されています。
また、iOS用の設定もmetaタグで追加されています。
将来的には、iOSでもmanifest.jsonをサポートするようです。

index.html
<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    Fore more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
  -->
  <base href="/">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="A new Flutter application.">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="flutter_web">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>

  <title>flutter_web</title>
  <link rel="manifest" href="manifest.json">
</head>
<body>
  <!-- This script installs service_worker.js to provide PWA functionality to
       application. For more information, see:
       https://developers.google.com/web/fundamentals/primers/service-workers -->
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('flutter-first-frame', function () {
        navigator.serviceWorker.register('flutter_service_worker.js');
      });
    }
  </script>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

ネイティブアプリとPWAの描画エリアの比較

iOSの場合、ネイティブアプリとPWAで描画エリアが違いました。
ステータスバーを黒→透明に変更して試してみます。

index.html
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">


▲ネイティブアプリとPWAの比較 (青が描画エリア、水色がSafeArea)

・描画エリアがステータスバーまで伸びていない。
・セーフエリアが正しく取得できていない。

ネイティブアプリのような見た目にしたい場合は、少し工夫が必要です。

Service Worker

index.htmlには、Service Worker というものが追加されていました。

Service Workerは、ブラウザがWebページとは別にバックグラウンドで実行するスクリプトです。
PWAにおいて、オフラインキャッシュプッシュ通知を提供します。
Service Workers: an Introduction - Google Developers

オフラインキャッシュは Android / iOS ともにできていたので、正常に動作しているようです。
プッシュ通知は、Firebase Cloud Messaging と合わせて使うようですが、iOS14ではまだPWAのプッシュ通知がサポートされておらず、Appleの対応を待つしかないようです。

まとめ

iOSのプッシュ通知以外は、FlutterWeb標準でPWAの要件を満たしていました。
FltterWeb自体のパフォーマンスが高く、Androidでは見た目も操作感もネイティブアプリと遜色ないので、小規模なアプリならPWAで充分やっていけそうです。

iOSではAppleがPWA推進派ではないので、どうしてもネイティブアプリと違う部分が出てきます。時間が解決してくれるはずなので、無理にネイティブアプリに合わせず、割り切るのも手かもしれません。

機能面では、Flutterパッケージの中にはまだFlutterWebに対応していないものがあるので、事前に調査が必要です。
また、PWA自体にまだ制約があるので、Android と iOS の対応状況をみながら、機能を見極める必要があります。

PWAの現状をについては、下記の記事が参考になりました。
PWA 入門 〜iOS SafariでPWAを体験するまで〜 2019年7月更新 - Qiita