はじめに
ウォンテッドリーで Mobile Chapter のリーダーをしている久保出です。Mendy の技術を紹介するリレー連載の 4 本目です。
1 本目では、Mendy のモバイルに Expo(React Native)を選んだ理由を紹介しました。今回は、実際に Expo で開発・運用してみて何が変わったのかを、弊社のネイティブアプリ「Wantedly」(以下 Wantedly アプリ)と比べながら書きます。
先に結論を書くと、Expo を選んで一番よかったのは、修正や改善をユーザーに届ける速さでした。iOS と Android を 1 つのコードで書けることよりも、この差のほうが大きかったと感じています。
速度の重要性
Mendy はリリースしたばかりの ToC プロダクトです。どんな体験が受け入れられるかは、出して反応を見るまで分かりません。仮説を出し、反応を見て、直して、また出す。このサイクルが短いほど、同じ期間に試せる回数が増えます。
Wantedly アプリでも速さはもちろん大事です。ただ、多くのユーザーに長く使われているプロダクトでは、速さと安定のバランスを取る必要があります。Wantedly アプリは、そのバランスを取るためにリリーストレインを仕組み化しています。一方で Mendy では、速度は速ければ速いほど良いと考えています。
Wantedly アプリと Mendy の届け方の違い
Wantedly アプリは、iOS と Android を毎週月曜日に段階的に公開するリリーストレインのフローにしています。コードフリーズからユーザーに届き始めるまで、審査や QA を含めて 3 営業日程度必要です。
Mendy では、JavaScript だけの変更なら、PR をマージするとストアの審査なしで自動的に配信されます。Expo のクラウドサービス EAS の EAS Update を使い、アプリのストアバージョンを出し直さずに JavaScript のバンドルを差し替えて届けています(いわゆる OTA)。
速さを発揮した例もあります。コア体験に関わる不具合をリリースしてしまったことがありましたが、修正に取りかかってから 3 時間ほどで直したバージョンを届けられました。リリーストレインであれば QA で気づけていたかもしれません。ただ、一度ストアに出してしまうと、修正版を届けるまでには審査を含めて時間がかかります。
どの変更を OTA で出せて、どの変更にストアの審査が必要なのかは、Expo の fingerprint で判定しています。fingerprint はネイティブ側の依存や設定から計算する値で、これが変わったときはネイティブのビルドが必要です。CI はこの値を見て、変わっていなければ OTA を、変わっていればストア向けのビルドから審査の提出までを自動で進めます。リリース以降にマージしたモバイルの PR のうち、ネイティブのビルドが必要だったのはごく一部で、9 割以上は OTA で出せました。
この仕組みを作るのも大変ではありませんでした。AI でコードを書きやすくなったことも大きいのですが、Expo 自体がビルド、証明書の管理、ストアへの提出、OTA の配信までをまとめて提供しています。Wantedly アプリは歴史的な経緯もあって複数のツールやサービスを組み合わせていますが、Mendy は GitHub Actions と EAS の 2 つで足りており、扱うツールは確実に減っています。
速さとのトレードオフ
速さと引き換えに、考えることも増えました。
まずは品質です。マージすればそのまま本番に届くので、不具合も同じ速さで届きます。リリーストレインのように毎回 QA を通す体制ではなく、開発者が変更ごとに QA が必要かを判断し、必要なものを QA チームに依頼しています。十分な体制とは言えず、今も課題の 1 つです。
次に、開発中の機能の扱いです。マージしたものはすぐに届いてしまうので、開発中の機能はフィーチャーフラグで隠す仕組みが必要になりました。OTA やフィーチャーフラグは、ストアの審査ガイドラインの範囲内で運用しています。
さらに、バックエンドとのデプロイの順番です。アプリの JavaScript がバックエンドより先に届くと、新しい API を呼べずに失敗します。CI で、バックエンドのデプロイが終わってから OTA を出すように順番を決めています。
最後に、古いバージョンへの考慮です。OTA で届けられるのは、ユーザーの端末にすでにあるストアバージョンが受け取れる変更だけです。インストール直後の最初の起動ではストアバージョンに同梱された古いバンドルで動くこと、古いストアバージョンを使い続けている人がいることを前提に、更新の待ち方やアップデートの案内を作り込みました。
ネイティブより快適だった開発体験
開発中の確認のしやすさも、Wantedly アプリより快適だと感じています。
- PR ごとにプレビュー用の OTA を配信し、QR コードを PR のコメントに付けています。開発用のアプリでこの QR を読むと、その PR の状態に切り替わります。レビューする人は手元でビルドせずに実機で動きを確かめられます。
- 端末を振ると開くデバッグメニューから、フィーチャーフラグの切り替えやサインアウト、チュートリアルのやり直しなどができます。本番のビルドからはコードごと取り除いています。
- 端末上で動く Storybook で、コンポーネントを単体で確かめられます。iOS と Android の表示の違いを見比べるのにも使っています。
SwiftUI や Jetpack Compose でも、プレビューや Hot Reload の対応が進んでいるので、この差は今後縮まっていくと思います。
Expo でもネイティブの知識は活きる
一方で、React Native 固有の問題にもいくつも当たりました。とくに多かったのはテキスト入力まわりです。
- iOS のログアウト後のログイン画面で、入力欄の placeholder が下にずれて文字の下端が欠ける。
- iOS で、日本語を変換している間の下線が出ない。
- Android で、文字色に color: transparent を指定しても透明にならず黒く表示される。
- Android で、複数行の入力欄がスクロールの操作を取ってしまい、画面がスクロールできない。
1 つ目の placeholder の問題は、React Native が破棄した TextInput のネイティブの部品を使い回すとき、前の状態が残るのが原因でした。React Native 側の Issue が未解決のため、暫定対処として Objective-C で使い回しを止めています。この修正はネイティブのコードなので、OTA では届けられず次のストアバージョンと審査を待つ必要がありました。速く届けられるのは JavaScript で直せる不具合だけです。
これらの調査では、最終的にネイティブ側のコードを読むことになりました。Expo と React Native は高度に抽象化されていて、TypeScript と React が分かれば誰でも書き始められます。それでも、画面を描いているのは結局 iOS と Android のネイティブの部品です。ネイティブの知識があるほど、原因を絞り込むのが速くなり、「ここは回避する」「ここは React Native 側の修正を待つ」といった判断もしやすくなります。
シングルコードベースをどう見るか
ちょうど Shopify が、React Native からネイティブに戻ると発表しました。コーディングエージェントのおかげで、同じ機能を 2 つの OS で作るコストが下がり、コードを共通にする利点が薄れた、という理由です。
1 つのコードで書くことには欠点もある、という点には同意します。Wantedly アプリは Kotlin Multiplatform でロジックを共通にしつつ、UI のコードは iOS と Android で分けて書いています。1 つのコードで書けることは、手間を減らす利点であると同時に、OS ごとの最適な作りから離れる欠点でもあります。
そのうえで、Mendy にとっては Expo が最適だったと考えています。理由は 2 つあります。1 つは、チームにネイティブに詳しい人が少なかったことです。もう 1 つは、Mendy が Expo から一番得たものが、コードの共通化ではなく届ける速さだったことです。AI でネイティブのコードを速く書けるようになっても、ストアの審査を待つ時間は縮みません。
AI が進むと、コードを書くこと自体の重みは下がっていくと思います。その分、抽象化の下で何が起きているかを理解し、技術的な判断を下せるかどうかが、むしろ差になっていくと考えています。
さいごに
Mendy のモバイルでは、Expo の OTA によって、作ったものをすぐにユーザーへ届け、反応を見て直すサイクルを短く回せています。その一方で、品質の守り方やデプロイの順番など、速さと引き換えに考えることも増えました。抽象化の下にあるネイティブの知識も、依然として欠かせません。振り返っても、新規プロダクトの立ち上げで届ける速さを最優先にした選択は正しかったと感じています。
次回は乙津さんが担当します。
ウォンテッドリーでは、一緒にプロダクト開発を行うエンジニアを募集しています。このストーリーに興味を持ってくれた方がいれば、ぜひお話ししましょう!