🛤️

【Rails × Flutter】既存のWebアプリのモバイル版をリリースするまでに学んだこと

2021/12/11に公開

BooQsのiOS/Android版をリリースしました。

先日、これまでWebアプリとして提供していた、絶対に忘れない英和辞書&英単語帳アプリ「BooQs」のiOS/Android版をリリースしました。

iOS版:
https://apps.apple.com/jp/app/booqs/id1594559036?itsct=apps_box_link&itscg=30200

Android版:
https://play.google.com/store/apps/details?id=com.booqs.booqs_mobile

技術構成としては、以下の図のように、すでにWebアプリとして提供しているRailsアプリにモバイル用のAPIを生やして、クライアントにFlutterを使いました。

さて、今回は僕にとって初めてのアプリ開発で、今までネイティブアプリを開発した経験もなければ、Flutterもゼロから学びながらのリリースでした。

また巷の記事の多くは、「Firebase × Flutter」という組み合わせで、既存のWebアプリにFlutterを掛け合わせた例を解説した記事はほとんどないということもあり、少々手こずりました。

先達の多いWebアプリと異なり、かなり手探りな開発体験でしたが、無事リリースできてよかったです。

この記事では、自分が「Rails × Flutter」でネイティブアプリをリリースする中で学んだことや、知っておきたかったことなどを記録しておこうと思います。

「Webアプリを運営しているのだけど、モバイル版も提供したいなぁ。でもモバイルアプリってなんかハードル高そうだなぁ」 と考えている以前の僕みたいな方には参考になるんじゃないかと思います。

利用させていただいた教材

Flutterを学ぶにあたっては、だらさんの下の本をかなり参考にさせていただきました。

https://zenn.dev/dala/books/flutter-basic-app

何か新しい技術を学ぶ上で、「何かチュートリアルに従って一通り動くアプリをつくってみて、それを自分の手で改造しながら理解を深めていく」というのは、なかなか悪くない学習法なのではないかと思います。

みよう見真似でも動くアプリが出来上がるのは楽しいですし、アプリを完成させて全体像を俯瞰できるようになった状態から、アプリを自分で改造してみることで、効率的に問題を特定できるようになったり、今まで呪文にしか見えなかった部分の理解が深まる、というのはよくあることだと思います。

少なくとも、僕はそういう身体で覚えるタイプの学習が性に合っていたようです。

なので、分量もコンパクトで、とりあえず動くアプリが作れる上記の本はかなり役に立ちました。

内容についても、僕が実際に使ってみて、動かなかった点や補足が必要だと思った点などはフィードバックして修正してもらったので、きっと役に立つはずです。

たとえば、多くの教材でFlutterアプリを作成するとき、

flutter create app_name

という説明がされます。
実際この通りにアプリを作成すると、Androidではcom.example(組織名).app_name、iOSではcom.example(組織名).appNameといった形でアプリのID(iOSならBundle Identifier、AndroidならApplication IDと呼ばれるもの)が設定されます。

しかし、これは罠で、そのままリリースまで進もうとすると、次のような問題が起きます。

  • Google play storeは、組織名がexampleであるアプリの登録を許可してくれないので、そもそもAndroidアプリとしてリリースできない。
  • 一度アプリをPlay StoreやAppStoreでリリースしてしまうと、アプリを削除しない限り、アプリIDを変更できなくなる。
  • リリース前なら変更もできるが面倒。プッシュ通知などのためにFirebaseにiOS/Androidアプリの登録をしていると、GoogleService-info.plistgoogle-service.jsonの変更も必要になってくる。
  • ダサい。

僕はこれのおかげで、一度ストアにリリースしたiOSアプリを削除する羽目になったりして超面倒だったので、これからFlutterアプリをつくる人には、

flutter create --org com.yourcompany(組織名) app_name

といった形で、初めから組織名を指定することを強く推奨したいです。

上の本には、こうした自分の細かな失敗などが反映されてるので、Flutterでアプリを作成してリリースまで目指しているなら、役に立つだろうと思います。

とくに、鍵の設定やストアでリリースするまでの手順など、コーディング以外の面倒な部分も説明されている点は、本当にありがたかったです。

著者のだらさんも、内容について積極的にフィードバックを受け入れてるので、不具合などがあっても連絡すればすぐに修正してくれると思います。

みなさんが買ってくれれば、きっとだらさんも改善するモチベーションが湧くはず!!笑

ぜひに。

APIとの通信

FlutterからRails APIとの通信には、httpというパッケージを使いました。
https://pub.dev/packages/http

自分は使ってはいませんが、他にも評判の良いものとしてはdioというパッケージもあるようです。
https://pub.dev/packages/dio

iOSではソーシャル認証を導入する場合、必ずApple認証も一緒に導入しなければならない

iOSでは、Google認証やTwitter認証などを実装する場合、必ず一緒にApple認証も実装しなければならないという決まりがあります。

なので、もしWebアプリのほうでソーシャル認証を導入している場合は、否応なく、Webとモバイル両方にApple認証も導入しなくてはならないでしょう。

幸い、Flutterにはsign_in_with_appleというパッケージもありますし、Webであれモバイルであれ解説記事は多いので、実装は難しくはないでしょう。
https://pub.dev/packages/sign_in_with_apple

とくにFlutterのクライアント側の実装では、悩むことは少ないと思います。
必要なら、BooQsのApple認証部分のコードは公開しているので、参考にされるといいかもしれません。

https://github.com/kawanji01/BooQs_on_mobiles/blob/main/lib/widgets/session/apple_button.dart

ただ、巷の記事の多くは、認証には「Firebase Authentication」を使っていたので、もしもRailsで自前の認証システムをもっていて、そこにApple認証を統合したい場合には、ちょっと面倒です。

なぜならWeb, iOS, AndroidでそれぞれApple認証の実装方法が異なるためです。

WebのRailsアプリの場合、以下の記事が参考になります。
https://qiita.com/kazama1209/items/a04c3a3e5e136a4a9ac7

Androidでの実装では、WebViewの認証画面を使うという点ではWebに似ていますが、コールバック先に設定したRailsのコントローラーのアクションで、次のようにintent URIを指定しなくてはならないというのがつまづきどころでした。

controllers/sessions_controler.rb
def apple_on_android
    # パラメーターをハッシュ化 参考: https://qiita.com/kanna/items/171118e479c2dc76bb7c
    q_hash = params.permit(:state, :code, :id_token).to_hash
    callback_params = URI.encode_www_form(q_hash)
    # intent uriについては、https://www.m3tech.blog/entry/android-webview-intent-scheme
    intent_uri = "intent://callback?#{callback_params}#Intent;package=com.booqs.booqs_mobile;scheme=signinwithapple;end"
    redirect_to intent_uri, status: 307
 end

一方、iOSでは、Apple認証はブラウザ経由ではなく、アプリ内部ですべて完結します。
そのため、WebやAndroidと異なり、認証サーバーから返ってきたidentity_tokenというJWTをRailsのコントローラーでデコードして認証する必要があります。
以下は、JWTのデコードして必要な情報を抜き出す部分です。

controllers/api/v1/mobile/sessions_controler.rb
def apple
	・
	・
   identity_token = params[:identity_token]
    res_json = JWT.decode identity_token, nil, false
    uid = res_json[0]['sub']
    email = res_json[0]['email']
	・
	・
 end

RubyでのJWTの取り扱いには、gem 'jwt'を利用しました。
また、認証サーバーから帰ってくるJWTについての説明は、下の記事が参考になりました。

https://qiita.com/kiwi26/items/5b8cc53ed8d10a403f00

もはや対応必須なApple認証が、ここまで手間がかかるのは意外でした。
できるなら自前で認証をつくるより、Firebase AuthenticationのようなAuthentication as a serviceを使うほうが簡単だし賢明なんだと思います。

Ready For Reviewは審査待ちじゃない!!

今までWebしか開発したことのない自分が、アプリを開発して一番驚いたことは、リリースされるまでの審査が長いということでした。
Androidは初回の審査以外はそれほど長くなく、数時間で終わることもあるのですが、iOSでは審査に2,3日以上かかることはザラで、デプロイして5分でリリースされるWebとのカルチャーギャップを味わいました。

これに関連して、僕はすごく恥ずかしいミスを犯したことがあります。

AppStoreの審査に出していないのに、審査に出したつもりになって、一週間待ちぼうけしたことがあるのです。

なぜ僕はこんな馬鹿げたミスを犯してしまったのでしょうか?

それは、アプリのステータスのReady For Reviewを「審査待ち」だと勘違いしていたからです。

iOSでは、アプリを申請に出してストアにリリースされるまでのそれぞれのステップで、アプリのステータスが変わり、その際にはメールを送って通知してくれます。

たとえば、「アプリを審査に提出する準備が完了」するとReady For Reviewとなり、実際に審査に提出すると「審査待ち」としてWaiting For Reviewとなり、実際に「審査が開始」されるとIn Reviewとなり、「審査が通ってストアに表示される」とReady For Saleとなる、といった具合です。

さて、お気づきでしょうか?
Ready For Reviewでは、実際にはアプリを審査に提出していないのです!
アプリのステータスがWaiting For Reviewになってやっと、2,3日以上かかる長い「審査待ち」が始まるのです。

本当、情けないミスなんですが、知り合いにもReady For Reviewを審査待ちと勘違いしていた方がいらっしゃったので、一応ご紹介しておこうと思います。

ストア画像の作成方法

アプリをリリースするためには、iOSなら最低限6.5inch,5.5inch,12.9inchの3種類のデバイスのスクリーンショットが必須です。
またAndroidでも、指定サイズの携帯電話のスクショのほかに、10inch,7inchのデバイスのスクショが必須です。

しかし、このストア画像の準備は必須であるにもかかわらず、この手順を解説してくれる記事はありませんでした。

なので、下に自分用ですが、ストア画像作成の手順をまとめています。

https://github.com/kawanji01/BooQs_on_mobiles/wiki/Store画像の作成手順

上記のWikiについては他にも、(本当に自分用にですが)アプリのリリースまでの手順(輸出コンプライアンス設定とか)などもメモしてあるので、よろしければ自由にご参考ください。

終わりに

ネイティブアプリ開発について、事前に知りたかったと感じた部分は、これくらいでしょうか。

またあとで何か思い出したら追記しますが、大体このあたりを押さえておけば、モバイルアプリの開発については、僕のようなWeb系開発者が以前に恐れていたほどハードルが高いわけではなかったです。

「とりあえず手元のWebアプリをモバイル化したい」というニーズに対して、Flutterはよく応えてくれてくれてるのではないかと思います。

もちろん、「既存のWebアプリのiOS/Android版を"とりあえず"リリースする」 という意識の低い目標に対するポイントなので、アプリを最適化する段になれば、「知っておくべきだった」と感じる部分はこれから先も増えていくでしょう。

自分自身、今でも良いコードをかけてる自信もないので、未来の自分が後悔するような「盲点」を進行形で持っているんだろうなと思います。

もしもそういう「盲点」を見つけましたら、Flutterのソースコードは公開しているので、プルリクエストやフィードバックをいただけると嬉しいです!!

https://github.com/kawanji01/DiQt

Discussion