React Native + Expo で加速する Qast モバイルアプリ開発の未来

本記事はany Development Advent Calender 13日目の記事です!

https://qiita.com/advent-calendar/2024/anyinc

## はじめに
any株式会社でプロダクトチームに所属している各務です！

現在、anyでは [Qast](https://qast.jp/) の**iOS/Androidモバイルアプリケーション**および**Webアプリケーション**の開発を担当しています。

今回は、**Qastのモバイルアプリ開発**における課題や、今後の展望について考えている事をお話しします！


## モバイルアプリの課題 
現在開発中のモバイルアプリに関して、以下のような課題があります。

- **モバイルアプリ開発におけるリソース不足**
anyにはモバイルアプリエンジニアがまだ少なく、iOS（SwiftUI）と Android（Kotlin）のそれぞれを限られたリソースで実装する必要があります。機能追加や改修など、やりたいことをスピーディーに進めようと思うと、リソースが足りていない状態です。

- **Webアプリケーションとの機能差**
Qastでは、エディターの一新やAI機能の追加など、次々と新しい機能がWebアプリケーションに実装されています。これらをモバイルアプリにも同等のレベルで適用しようとすると、現行の構成では莫大な工数とリソースが必要となり、対応が遅れる傾向にあります。

これらの課題を解決するためには、Webアプリケーションの資産を活用し、Webエンジニアが持つ技術力を活かす方針が有効だと考えています。

## 課題解決に向けて

### 解決策の検討
上記の課題解決をする上で、まずは実装にかかるリソースを最小限に抑えることが重要だと思っています。
そのため、**クロスプラットフォームフレームワーク**を活用するアプローチを検討しています。

以下のフレームワークが候補として挙げられます。


| フレームワーク | メリット | デメリット |
| ---- | ---- | ---- |
| Flutter | ・単一コードベースでiOS/Androidに対応 <br> ・高いカスタマイズ性とリッチなUIが構築可能 | ・Dartという専用の言語を学ぶ必要がある <br>・Web資産（Reactなど）の再利用が難しい|
| React Native + Expo | Reactベースのため、既存のWeb資産やWebエンジニアの知見を活用可能 | 一部ネイティブコードが必要になる場合、Expoの制約が発生する可能性がある |
| lonic + Capacitor | Webアプリ資産をそのまま活用できる | モバイル特有のUI体験を求められる場合、カスタマイズに制限がある |


### どのフレームワークを選ぶべきか？
現状の課題やリソースを踏まえると、**React Native + Expo** を選択肢として優先的に検討するのが有効かと思います。
特に、Qast では既に Web アプリケーションで React を活用しているため、Web エンジニアの知見をそのまま活かせる点が大きな強みとなります。
また、 Expoを活用することで**ビルド環境のセットアップやモバイル特有の処理が簡略化される**だけでなく、**機能開発の効率を大幅に向上**させることが期待できます。

では、**React Native + Expo** を活用したクロスプラットフォーム開発で、どのように課題を解決できるのか、具体的に見ていきましょう！

## React Native + Expo をベースにしたクロスプラットフォーム開発


### 1. Fastlaneを使った証明書発行、テストフライトをExpo EASへ乗り換え
既存のプロジェクトでは、**Fastlane match** を使った証明書発行や、**GitHub Actions + App Store Connect API + Fastlane** を活用してCI/CDを構築し、TestFlightへのビルド配信を実現しています。

https://docs.fastlane.tools/actions/match/

しかし、この仕組みには以下の課題が伴っています

- **CIのXcodeバージョン更新が必要**
Appleの新しいOSやXcodeのリリースに伴い、CI環境のアップデートが定期的に必要になります。

- **Fastlaneのライブラリや設定のメンテナンスが発生**
Fastlaneライブラリや証明書管理のためのGitリポジトリにおける定期的な更新作業が求められます。

- **Rubyでの設定変更が開発工数を増加させる**
Fastlaneの設定ファイルはRubyで記述されており、普段Rubyを使わない開発者にとってメンテナンスの工数が増加する要因となっています。

これらの課題を解決する手段として、Expoの **EAS（Expo Application Services）** を利用することで、以下のような利点が得られます。

- **証明書管理の簡略化**
EASはApple Developerアカウントと連携し、必要な証明書やプロビジョニングプロファイルを自動で生成・管理します。証明書のローカル管理が不要となり、Gitリポジトリでの証明書共有やFastlane matchの設定が不要になります。

- **CI/CDの簡素化**
EASのビルドシステムはExpoのエコシステムに統合されており、特別なスクリプトを記述せずにTestFlightやApp Storeへの配信が可能です。

- **メンテナンスコストの削減**
EASはReact Native Expoプロジェクトに最適化されているため、設定作業が容易で、RubyやFastlaneのライブラリ更新作業が不要になります。

https://docs.expo.dev/eas/


### 2. OTAアップデート
従来、アプリの更新はストアを通じて行う必要があり、軽微な変更でも審査を待たなければなりませんでした。しかし、Expo の **OTA（Over-The-Air） アップデート機能** を利用すれば、コードの変更を即時反映できます。

この仕組みは、特に 緊急対応や頻繁な軽微変更 が求められる場面で重宝するため、開発チームの柔軟性を大幅に向上させると考えています。

https://docs.expo.dev/eas-update/introduction/

https://docs.expo.dev/eas-update/how-it-works/

### 3. applinks, universal links
Qast では、投稿に紐づく URL を通じて、その URL を開いた際にアプリの該当画面が表示されることが、ユーザビリティ向上の観点で重要だと思います。これを実現するためには、モバイルアプリにおける App Links（Android） と Universal Links（iOS） の設定が欠かせません。

従来、これらのリンク機能を実装するにはプラットフォームごとの設定が必要で、煩雑になりがちでした。しかし、Expo の Linking API を活用することで、以下のような利点があります。

- **クロスプラットフォーム対応**： Android と iOS の両方に対応した実装が可能
- **実装のシンプルさ**： 必要な設定やコードが簡素化されており、Expo のドキュメントが詳細にサポート

https://docs.expo.dev/linking/overview/


## まとめ
この記事では、Qast のモバイルアプリ開発における課題と、それを解決するための手段として React Native + Expo を活用するアプローチを提案しました。

- Fastlane から EAS への移行により、証明書管理や CI/CD の負担を軽減
- OTA アップデートで緊急対応や軽微な修正を迅速化
- App Links / Universal Links の実装でユーザビリティを向上

さらに、既存の Web アプリケーション資産や Web エンジニアの知見を最大限活用することで、モバイルアプリ開発の生産性を飛躍的に高めることが可能です。React ベースのエコシステムを活かした開発は、Web とモバイル間の一貫性を維持しながら効率を最大化できると思います。

Qast のモバイルアプリ開発は、効率化だけでなく「エンジニアが創造性を発揮できる開発環境」を構築できれば良いなと考えています。これからも新しい技術や工夫を取り入れながら、ユーザーに価値を届けられるアプリを作り続けていきたいと思います！

***

any株式会社では[ナレッジ経営クラウドQast](https://qast.jp/)のエンジニアを絶賛募集中です。
是非[採用ページ](https://careers.anyinc.jp/)をご覧ください！
興味がある方は、[こちら](https://hrmos.co/pages/anyinc/jobs?category=1938133004283695106)よりご応募お待ちしております。
エンジニア組織/文化について詳しく知りたい方は[こちら](https://speakerdeck.com/any_tech/engineer-culture-deck-v3-dot-0)