id:kikuchy です。
先日、いよいよバクラク申請・経費精算のiOS/Androidアプリがリリースされました!
こちらの2つのアプリ、現在のところ専任のエンジニアはkikuchy一人だけで開発しています。
そのためか、技術スタックは何なのか、クロスプラットフォームフレームワークの技術を使っているのか、といった質問をたくさんいただきました。
また、LayerXのモバイルアプリ第一号でもあるため、リリースまでにいろいろな準備が必要でした。
当記事では、どんな準備をしてきたのか、使用技術スタックとその選定、開発の苦労話の一部をお伝えできればと思います。
(社内で)初めてのアプリ開発
会社はすでにプロダクトを展開していて、しかしスマホアプリ開発にはまだ手を出していない。
そんな環境でアプリ開発を初めるというのはなかなか珍しい経験だと思います。
(とくに現代のC向けビジネスは真っ先にアプリから作り始めますからね!)
私としても初めての経験でした。
そうした環境では、開発を始めるよりも前に行わなければならないことがあります。
制度の整備
まず、会社の備品として開発検証機がありません。
これまで開発検証機が必要なかったということは、それを扱うための制度もありません。
AppStore / PlayStoreのアカウントもありません。
LayerXではCTO室がセキュリティやコンピューター系の備品、クラウドサービスのアカウントの取り扱いについての制度を定めたり、管理したりしています。
CTO室の皆様にご尽力いただいて、以下のものを決めたり、用意していただきました。
- 端末取り扱いの方針策定
- 保管場所
- 端末の状態管理台帳
- ロックパスワードや、現在のOSのバージョン、誰が使用中か、など
- Apple Developer programの法人登録
- Google Play Developerの法人登録
検証機の選定
社内制度的には検証機を取り扱えることになったので、いよいよ検証機の入手です。
とはいえ、予算は無限ではないので、少ない端末数で多くの利用者環境を再現できることが求められます。
特にAndroid端末は種類が豊富なので、なるべく想定利用者層がメインで使っていそうな端末を揃えることが重要です。
今回は以下の方針で端末を取り揃えました。
- アプリだけでなくスマホブラウザのサポート向上にも使用できるようにする
- QA効果が高いものを選ぶ
- 画面サイズがかぶらない
- シェアが高い機種
- 私用携帯を使う機会も多そうなので、C向けと同様に勘案
- 利用者の年齢層的に、年配者向けの機種などは除く
- 新しく、後続機種が登場する可能性が高い
技術スタック
kikuchyはFlutterKaigiの運営メンバーでもあり、Flutter系の勉強会でもたくさん登壇させていただいています。
そうした背景から「バクラク申請・経費精算アプリもFlutterで開発したのか?」というご質問をたくさんいただきました。
が、Flutterは使っておりません!
それどころかクロスプラットフォームフレームワークは未使用です!
両OSともネイティブ開発!
どうしてこの技術選定に至ったのかを解説したいと思います。
そもそもアプリを作らないつもりでいた
バクラク申請・経費精算のフロント部分はNuxtで作られたSPAになっています。
大部分のビジネスロジックはAPIサーバーの方で実装されていますが、スムーズな使い心地を実現するため、フロント部分にもそれなりのロジックが実装されています。
kikuchyの入社時点(2022年4月半ば)では、申請・経費精算チームのエンジニアは3人体制。
プロジェクトのやりたいことリストはすでに山盛り。
この人数で、webのフロントだけでなく、アプリも機能追加&メンテナンスするのは無理だと判断し、最初期のマネージャーとの 1 on 1でも「当面アプリは開発しない」という意見で合致したのを覚えています。
そのため、まず始めに目指したのはスマホブラウザ対応でした。
PCからの使用を前提としてデザインされていたがためにスマートフォンからは使用できない機能などがあったため、一部のUIをスマートフォン向けに切り替えるなどして、スマートフォンからも使用可能にしたのです。
ユーザーインタビューなども行いながら、1ヶ月程度の改修を行いました。
スマホブラウザだけでもかなり使いやすいように改修できたかなと思っています。
やっぱりアプリじゃないと
ところが、スマホアプリでなければ実現不可能な機能がKoF (Knockout Factor: この問題が解決できないとお客様に導入していただけない要素)として積み上がって来てしまいました。
お客様に喜んでいただけるバクラクな体験を作るためにも、この機能はぜひ実現したい…
(どんな機能なんでしょうね!今後のリリースをお楽しみに!)
と、いうことで、6月頃に方針転換をしてスマホアプリを作ることになりました。
この時点で決まったのは以下の方針です。
- 可能な限り多くのお客様に、
- 可能な限り早くバクラクな体験をお届けする
そこから検討した結果が以下の実装方針です。
- iOS/Androidの両OS対応
- 可能な限り現在の資産(コードベース)を活用する
すでにスマホブラウザで動作するのですから、WebViewアプリにするのが妥当でしょう。
ということでWebViewアプリを用意する方針で進み始めました。
クロスプラットフォーム技術は…
2022年現在、新規にアプリを開発するとなると、FlutterやReact Nativeなどのクロスプラットフォーム技術の使用を真っ先に検討する方が多いのではないでしょうか。
単一のコードベースで複数のOSに対応できるという利点は、開発やメンテナンスのコスト削減に大きく貢献します。
当然、クロスプラットフォーム技術の使用を真っ先に検討しました。
が、以下の理由で断念します。
- Flutter
- WebViewとの相性が悪い
- 現在の公式WebViewとも呼べる webview_flutter は Androidで input type="file" のHTML要素が動作しない 問題を抱えています。領収書の画像を撮影して添付してもらいたいのに、これが動作しないのは致命的です
- すでに廃止が決まっている flutter_webview_plugin ならこの問題は起こりませんでしたが、(FlutterActivityの上にWebViewを表示しているためFlutterの描画環境では不格好に見えるというのも含め)今後メンテナンスされる保証がないので使用を諦めました
- 他にもwebview系のプラグインの動作を検証しましたが、 webview_flutter とほぼ同様でした
- WebViewとの相性が悪い
- React Native
- WebView系の機能は概ね問題なし
- 採用の観点で問題あり
- LayerXにはCTO経験者が何人も(!)います
- 採用市場で不利という意見が多数
- Google Trendsなどでもここ数年人気が振るわないのが見て取れるため、断念
両OSネイティブ開発できるの?
実際のところ、WebViewとそれにまつわるロジックが主であるため、そんなに苦労することなく実装できました。
前職で両OSとも1から開発する機会があり、開発経験を詰んでいたから、ということもあります。経験を積ませてくださった前職にも感謝🙏
実装の苦労・工夫
とはいえ、単純に WebViewアプリ = 簡単
というわけではありません。
苦労を乗り越えたり、工夫を要した点はいくつかあります。
Googleログイン
バクラクシリーズはGoogleアカウントを使ったSNS認証(いわゆる Googleログイン
)に対応しています。
ところがGoogleログインは、標準のWKWebView / WebViewではできないようになっています。
当アプリはWebViewアプリであり、WebView内のセッションでログインできないと意味がありません。
「初回リリースではオミットしてしまおうか…」という意見もありましたが、たくさんのお客様がGoogleログインを使用してくださっているため、初回リリースのスコープでGoogleログインをサポートすることに決めました。
アプリ内でGoogleログインを始めとする認証フローを実行するための仕組みとして、iOSには専用の仕組みが用意されており、AndroidでもChrome Custom Tabsを使用することで同様のことが可能です。
これを実現するにはサーバー側含めいろいろな改修が必要でしたが、これにより、バクラク申請・経費精算アプリでもGoogleログインをお使いいただけるようになりました。
iOSの署名管理問題
バクラクシリーズには全部で4つの開発環境があります。
- Local環境
- 開発者の手元
- Develop環境
- 社内で開発中のバージョンを試すための環境
- Staging環境
- 主にリリース前のQAの際に使用する環境
- Production環境
- お客様にご提供する環境
このうち、Develop環境とStaging環境で使用できるiOSアプリをAdHoc証明書で署名し、Firebase AppDistributionで社内用に配布しようと検討していました。
また、Local環境で開発するエンジニアは今後増えます(増えてほしい 🙏)ので、Xcode ManagedなDeveloper証明書を使用したいです。
もちろん、Production環境はAppStore署名です。
ただ、Local環境以外のビルドは各開発者の手元では行わず、CIで行えれば十分です。
そのため、以下のように設定を行いました。
- 各環境用にSchemeを分割
- 全環境の署名を
Automatically manage signing
に設定 - AdHoc, AppStore署名はFastlane Matchでprivate repositoryで管理しておく
- CIでのビルド時にFastlaneの
update_code_signing_settings
で使用する署名の設定を差し替える
現在のところはこれでうまく動いています。
あとはiOSエンジニアが増えてくれればLocal環境の設定がこれで良かったことが分かるんだけどなぁ〜〜誰か来てくれないかなぁ〜〜 (/ω・\)チラッ
AndroidのBackボタン対応
Android API Levle 33にて、Activity#onBackPressed()がdeprecatedになりました1。 つい最近にリリースされたAndroid 13の予測型「戻る」ジェスチャー対応の一環と思われます。
WebViewアプリであれば、Backボタンが押下された(または戻るジェスチャーがなされた)タイミングで前のページに戻る動作をさせたいですよね。 ですが同時に、新しくリリースするアプリなのでなるべく新しい環境に対応しておきたいものです。
戻る動作制御用の新しいAPIは OnBackPressedCallback
です。
が、これは今までの Activity#onBackPressed()
とは動作モデルが異なります。
Activity#onBackPressed()
は、戻る動作が為されるそのときに、戻るか否かの判断を行うものでした。
代わって OnBackPressedCallback
は、戻る動作が為される よりも前に 、当該コールバックを行うか否かを設定する必要があります。
これはWebViewのインスタンスとあまり相性がよくありません。
WebViewには、 canGoBack()
2 が変化したか否かをインスタンス外部に通知する機能がないためです。
この問題は WebViewClient#doUpdateVisitedHistory()
3と組み合わせることで解決できます。
このメソッドはブラウザのアクセス履歴が変化したときに更新されるため、 history.pushState()
によって戻ることが可能になった場合にも呼び出されます。
// 使用イメージ val onBackPressed = onBackPressedDispatcher.addCallback(this, false) { webView.goBack() } webView.webViewClient = object : WebViewClient { override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) { onBackPressed.isEnabled = view.canGoBack() ... } }
バクラク申請・経費精算はNuxtによるSPAなので、これによってキチンとBackボタンでの戻る動作が実現できました。
Github Actionsのmacos: latestインスタンスがlatestではない問題
2022年9月現在、Github Actionsで使用できるmacOSのインスタンスで最新のものは macos-12
(macOS Monterey 12)です。
が、 jobs.(jobのID).runs-on
に macos-latest
を指定すると macos-11
が使用されます。なんでや!!!
GitHub Actions のワークフロー構文 - GitHub Docs
-latest ランナー イメージは、GitHub が提供する最新の安定したイメージであり
と書いてありますね…安定してないのかmacos-12…
ドキュメントを読まない私が悪いのですが、この問題のせいでXcodeのバージョンが期待したものにならずに、無駄にCIの時間を使ってしまいました(macosインスタンスはlinuxインスタンスの10倍高いのです💸)。
おわりに
とりあえず両OSともリリースが済んでホッとしています。
コードは私が書きましたが、決して私一人でリリースできたものではありません。
チームメンバーの協力や、爆速開発を支える会社の文化があって初めて実現できるものです。
まだまだ不足している機能や、リリースを控えている機能がありますので、今後のアップデートにもご期待ください。
せひ、手のひらの上でもバクラクな体験を楽しんでいただければと思います。
また、LayerXではモバイルアプリのリードエンジニアを大募集中です!
現在はWebViewアプリですが、「このままではお客様に十分な価値提供ができない」と判断したら別の実装に乗り換えることは十分に考えられます。
今後、アプリの領域でも技術選定を含めた意思決定の機会はまだまだ数多くあります。
アプリという側面から、バクラクな体験をお客様にお届けできる意思決定ができるリードエンジニアを待望しております!
-
https://developer.android.com/reference/android/app/Activity.html#onBackPressed():~:text=public%20void%20onBackPressed%20()-,This%20method%20was%20deprecated%20in%20API%20level%2033.,-Use%20OnBackInvokedCallback%20or↩
-
https://developer.android.com/reference/android/webkit/WebView?hl=en&authuser=1#canGoBack()↩
-
https://developer.android.com/reference/android/webkit/WebViewClient?hl=en&authuser=1#doUpdateVisitedHistory(android.webkit.WebView,%20java.lang.String,%20boolean)↩