日本のコロナウィルス感染数などを表示するAndroidアプリをFlutterで作ったけど審査通らなかった話
想像力の欠如
tl; dr
それはそう
ストアの掲載に関するガイドライン
Google Play は、ユーザーに正確で関連性のある情報を提供するという責任を果たすことに真摯に取り組んでいます。 そのため、現在、ストアの掲載情報で 新型コロナウイルス感染症(COVID-19) とそれに関連する表現を使用できるのは、政府機関または公的医療機関によって公開、委託、または承認されたアプリのみとさせていただいています。加えて、広告、アプリ内アイテム購入、アプリ内寄付などの収益化メカニズムが含まれていないアプリであることも条件です。なお、ストアの掲載情報には、アプリ名、説明、リリースノート、スクリーン ショットなどが含まれます。 この条件に該当しないアプリが 新型コロナウイルス感染症(COVID-19) や関連する表現を使用された場合、ストアへの掲載が承認されなかったり、ストアから削除される場合がありますのでご注意ください。
はい...
リジェクトされた時の文言がかなり強めでギョッとしたんだけど、このガイドライン自体は心の底から正しいと思う。
なんで作ったの?
コロナウィルスに関する情報を閲覧できるWebサービスは結構あるんだけど、モバイルアプリが全然なかった。その一方で、公開されていてかつ利用可能なデータが多かったので、じゃあ自分で作るかとなって作った。*1
iOS版は作らなかったの?
AppStore Connectのサブスクリプションが高い(11,800円)のでそもそも申請してない。もちろんAppleのガイドラインもGoogleと同じ。
https://developer.apple.com/jp/news/?id=03142020a
Flutterで作ったんすよ
ソース自体はMITライセンスで公開しているので、供養としてここに記す。
Firebaseの設定ファイルが無いとビルド通らないので注意。試してないけど、pubspec.yaml
からFirebaseに関するパッケージを消せば動くと思う。
dependencies: // 以下の3つを消して flutter pub getを実行 firebase_core: ^0.4.4+3 firebase_analytics: ^5.0.11 firebase_crashlytics: ^0.1.3+3
アプリの画面はこんな感じ。全国と都道府県別の情報を閲覧できる。
表示しているデータはどこから取ってきてるの?
東洋経済オンラインの新型コロナウイルス国内感染の状況というページに表示されているものと同じデータを使用させてもらった。
データ自体はGitHubのこちらのリポジトリで毎日更新されているようだった。
@kaz_ogiwara 突然のリプライ失礼します。萩原さんが公開されているリポジトリ(https://t.co/YFOD3iWhY7)のデータを使用し、AndoridアプリのソースコードをMIT
— osamtimizer (@osamtimizer) 2020年5月2日
ライセンスで公開しました。データの利用などに関して何か問題があれば、ご連絡いただければと思います。https://t.co/aQyYWWeSiR
Androidアプリ公開に関する一連のフローを体験できた
審査は通らなかったけど、Flutterでイチから自分でアプリ完成までコード書けたのと、Google Play Consoleでアプリの申請に必要なものごとを体験できたので、思っていたより収穫があった。*2
Flutterの知見や、審査を申請する上で知らなかったものが結構あったので、ここに記しておく。
Flutter
エラーハンドリング
iOSネイティブだったらエラーはViewControllerの層までrethrowし続けて、ユーザーに伝える必要があったらエラー用のViewを表示するといった処理を書くことが多い。これは手続き型UIプログラミングの強みである何かが起こったら、こうする
といった記述がしやすいため、問題になることは多くないと思う。
一方で、Flutterは宣言的にUIを記述するため、このようなアプローチがうまくいかないケースが出てくる。
FlutterにはScaffoldという、画面上部のナビゲーションバーを伴った一枚の画面を表示するためのウィジェットがあって、
このScaffoldにはshowSnackBar
というメソッドを呼ぶことで、画面下部にスナックバーを表示する機能がある。
表示するためにはScaffold.of(context).showSnackBar
といった感じで、現在のウィジェットよりも上位のScaffoldに対してメソッドを呼び出す必要がある。
このアプリはProvider パターンで作っていたので、APIへのリクエストを要求する層ととエラーを表示したい画面が一致しないことが多く、単純に例外をrethrowするだけでエラーメッセージを伴ったスナックバーを表示するのは難しかった。
具体例としては、APIへリクエストを要求する処理をScaffoldの生成より先に行いたい場合などが挙げられる。
どうしたものかと割と後半までエラーハンドリングを放置していたんだけど、ある時ふと、APIから取得したデータと同じように、エラーメッセージを購読可能にすればいいと気づいた。
で、エラーメッセージを表示したいウィジェットの層でエラーを購読することでうまくハンドリングができるようになった。
// store class TabBarStore extends ChangeNotifier { int _currentIndex = 0; int get currentIndex => _currentIndex; String _errorMessage; String get errorMessage => _errorMessage; ... } // subscription final tabBarStore = Provider.of<TabBarStore>(context, listen: false); tabBarStore.addListener(() { if (tabBarStore.errorMessage != null) { final snackBar = SnackBar( content: Text("エラーが発生しました。通信環境を確認するか、時間を置いてから再度試してみて下さい。"), action: SnackBarAction( label: "閉じる", onPressed: () { tabBarStore.clearError(); }, ), ); // エラーを表示したいウィジェットに最も近い上位のScaffoldに対してスナックバーの表示を要求する Scaffold.of(context).showSnackBar(snackBar); } });
購読が少し回りくどいように見えるかもしれないが、Provider.of<TabBarStore>(context, listen: false)
といったように、明示的にlisten: false
を指定しない場合、エラーメッセージが更新された瞬間にリビルドがかかってしまう。
Flutterではbuild
メソッドの途中でリビルドが発生すると例外を吐く上に、build
メソッド自体は頻繁に呼ばれるので、不要なリビルド要求は避けていく必要がある。
エラーメッセージが更新されたら、こんな感じにスナックバーが出るようになる。
具体的な実装はこの辺に書かれているので、気になった方は確認してみてほしい。
審査
プライバシーポリシーが記述されたWebページが必要
アプリの審査に申請するには、プライバシーポリシーが掲載されたWebページのURLが必要だった。ので作った。
https://covid-19-viewer-document.now.sh/
Flutterに結構慣れてきたので、Flutterに似た概念のReactを採用しているNext.jsに入門して、その勢いでプライバシーポリシーのページを作った。
作ったページはVercelにデプロイされるようにした。*3
VercelにはGitHubのmasterブランチへのpushやmergeをトリガーにしてデプロイする機能があったのでそれでデプロイした。
ドメインも.now.sh
が末尾につくだけなので、特別に何かを管理することがなくてとても楽。
おわりに
Googleのアプリ審査の時間をいたずらに浪費させてしまったことは素直に申し訳なかったと思う。 その一方で、Play ConsoleやFlutterに関する理解は深まったので、それはそれで良しとしたい。