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

この記事は、
[softdeviceのみんなでゆるく記事を書く。 Advent Calendar 2020](https://adventar.org/calendars/5794)
の12/17の記事として公開しています。

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

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

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


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

[プログレッシブウェブアプリの紹介 -MDN web docs](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Introduction)
[What makes a good Progressive Web App? -Google web.dev](https://web.dev/pwa-checklist/)
 
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](https://firebase.google.com/docs/hosting?hl=ja) を利用すると手軽です。
2〜4 は、少し前まで手動で行う必要があったのですが、現在は要らなくなりました。
Flutterをビルドすると自動で作成・設置されます。

ということで、こちらの手順を進めると、PWAを試すことができます。
[【FlutterWeb】Firebase Hosting でWebアプリを公開する - Qiita](https://qiita.com/fukusin/items/c5f5592b50101dfaea25)

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

▼Android
![android_pwa2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/d8e87fc9-5b3d-b353-d112-45605c978c36.gif)
▼iOS
![ios_pwa2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/064741eb-244f-aa9b-16a2-823c86e07084.gif)
▼Mac Chrome
![mac_pwa2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/cf220c23-bbee-f6dd-39d6-76e0ec111f46.gif)




#簡単に解説
FlutterWebを有効にすると、Webフォルダが追加されます。
このフォルダがビルドのテンプレートとなります。
![スクリーンショット 2020-12-17 12.40.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/eb040962-9bdb-c126-3006-078000e121c2.png)
##iconsフォルダ
ホーム画面に追加した時のアイコンです。
![スクリーンショット 2020-12-17 12.45.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/d355f4bb-48ef-1f02-62ee-13e6a5ff9620.png)

##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](https://app-manifest.firebaseapp.com/)というサイトが便利です。
アイコンのサイズ変換とmanifest.jsonの生成が簡単にできます。

各要素については、下記のリンクが参考になります。
[Web App Manifest - MDN web docs](https://developer.mozilla.org/ja/docs/Web/Manifest)
[Add a web app manifest - Google web.dev](https://web.dev/add-manifest/)

##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">
```

![safearea2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535406/3f2fda1e-115e-e4a1-0578-2f82958acf92.png)
▲ネイティブアプリとPWAの比較 (青が描画エリア、水色がSafeArea)

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

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

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

Service Workerは、ブラウザがWebページとは別にバックグラウンドで実行するスクリプトです。
PWAにおいて、**オフラインキャッシュ**や**プッシュ通知**を提供します。
[Service Workers: an Introduction - Google Developers](https://developers.google.com/web/fundamentals/primers/service-workers)

オフラインキャッシュは Android / iOS ともにできていたので、正常に動作しているようです。
プッシュ通知は、[Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging?hl=ja) と合わせて使うようですが、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](https://qiita.com/umamichi/items/0e2b4b1c578e7335ba20#pwa%E3%81%A8%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E6%AF%94%E8%BC%83)