スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Flutterによるネイティブアプリのリプレースプロジェクトを完遂しました

こんにちは、モバイルアプリエンジニアの @mtbhiro です。現在はスタディサプリ for SCHOOL(以下 forSCHOOL )1というプロダクトのモバイルアプリ開発を行っています。

Flutter による forSCHOOL アプリのフルリプレースプロジェクトを、2023年8月に Android アプリを、2023年12月に iOS アプリをリリースすることで完遂しました!

この記事ではどのような背景や目的でリプレースを行うことになったのか、どのようなプロジェクト管理を試みたのか、そして最終的にどのような結果が得られたのかについて紹介します。

forSCHOOL アプリについて

リプレースプロジェクトの紹介の前に、簡単に forSCHOOL アプリについて紹介します。

スタディサプリは学びに関する様々なサービスを提供していて、学生向けの動画で授業を受けられるようなアプリや、社会人向けの英語学習アプリなど様々なアプリがあります。

その中でも forSCHOOL は「学生の進路選択を支援する」ことを目的として展開しているアプリです。勉強したい学問や興味のある仕事を発見できる機能、学校の資料請求やオープンキャンパスの参加予約ができる機能、先生とのチャット機能など、学生が進路を選択する際に必要となる様々な機能を提供しています。

forSCHOOL アプリのユニークな点として「学校の授業の中で利用される」という点があります。

学校では生徒が自身の進路について考える授業を展開していて、forSCHOOL アプリは授業をスムーズに進行させるための機能も提供しています。また授業の中で先生や生徒が迷わずに forSCHOOL アプリを使えるようにする必要があるため、アプリ全体としてそんなユースケースに耐えられるようなデザインで構築されています。

もちろん生徒が個人的に利用できるアプリでもあるのですが、このように先生が授業の中のツールとして forSCHOOL アプリを利用することが前提となっている点が forSCHOOL アプリの特徴です。リプレースプロジェクトもこの特徴をよく理解しながら進める必要がありますが、それについては後ほど紹介します。

開発チームが抱えていた課題

Flutter によるリプレースを行った理由は色々ありますが、ここでは開発チームが抱えていた大きな二つの課題を紹介します。

開発リソースの確保が困難

一つ目の課題はおそらく多くの会社が抱えているであろうもので「開発リソースの確保が困難な点」です。開発リソースの確保が困難なのは「プラットフォームごとの開発により開発リソースが分散」と「モバイルエンジニアの採用が困難」の大きく二つの問題点が考えられます。

まず一つ目の問題点として「プラットフォームごとの開発により開発リソースが分散」という点について説明します。iOSAndroid の両方のプラットフォームでアプリを展開したい場合、当然ですがそれぞれのプラットフォームで同じ仕様のアプリを開発しなければなりません。これはやはり大きなコストです。iOSAndroid で同程度の開発リソースを確保できなければ足並みを揃えた開発が難しく、ソースコードの品質も同程度のものを維持するのも困難です。

また forSCHOOL アプリは toB の側面が強いことからも、プラットフォーム間による仕様差分はユーザーの混乱を招くため、可能な限り仕様を揃えて提供する必要がありました。そのため「iOS アプリだけ先行してリリース」のような選択肢を取ることは難しく、それぞれのプラットフォームで同じ開発リソースを確保できないと開発スピードが上げられないという問題が起きていました。

それに加え、二つ目の問題点として「モバイルエンジニアの採用が困難」という点がありました。我々のチームとマッチングするような人材を、そもそもモバイルエンジニアが少ない転職市場から採用することが難しく、結果的に1年採用活動しても1人もマッチングした人材を採用できないという結果に直面していました...。 ただでさえ母数の少ない人材で、プラットフォーム間で同程度の開発リソースを確保し続けるのはなかなか困難であることは、アプリを運用している会社のよくある悩みどころなのではないかなと思います。

プラットフォーム間の仕様差分によるコスト

二つ目の課題は「プラットフォーム間の仕様差分によるコスト」です。

先述した通り、forSCHOOL アプリは toB 的な側面を持っていて、純粋な toC アプリと比較すると、プラットフォーム間での仕様が異なることについてユーザーが敏感になりやすい側面があります。仕事の一部として forSCHOOL アプリを利用するユーザーからすれば、ちょっとしたUIの違いや挙動の違いでも敏感になるのも理解できます。そのことから結果的に、開発運用側にはちょっとした挙動の違いにしか感じられない差分が、利用するユーザーにとっては過剰に不安を感じてさせてしまう差分になる可能性がありました。また仕様差分による問い合わせに対応するには、カスタマーサポートやプロダクトマネージャーをはじめ、QAや開発エンジニアなどチームにまたがって対応する必要があります。そのため小さな仕様差分でも多くの人に割り込み作業が入ってしまうので、一つ一つは小さなものでも全体を見れば案外大きなコストに繋がっていると感じていました。

しかしプラットフォームごとにアプリを実装する以上、プラットフォーム間で違いが出てしまうこと自体は仕方のないことです。

仕様を無理に合わせようとしても、 iOS アプリ開発Android アプリで作りやすい仕様に違いがあるので歪な構造になりますし、仕様の違いを許容するとしても、仕様として何が正しいのかが分かりづらくなり、QA やカスタマーサポートの負担が増大したりします。

これらのことから根本的な解決方法を考えることは難しく、結果的に問題が出たらその都度対応するという、対処療法的なアプローチを取ることしかできていませんでした。

Flutter導入にあたって

以上の課題を抱えていましたが、マルチプラットフォーム開発ができる Flutter を採用することで、「開発コストの減少」と「仕様差分を最小化する」効果が見込めるのではないかと考えました。コードベースが一つであれば「全体の工数削減」「プラットフォーム間の仕様の最小化」を見込むことができ、上記の課題を解決できそうです。

とはいいつつ、もちろん恩恵だけを得ることは不可能で、Flutter を採用することによる制約やリプレースを行うコストも受け入れなければなりません。 以下に Flutter を導入することを決定できた大きな3つの理由について説明します。

アプリの特徴とFlutterの制約

1つ目の理由は「forSCHOOL アプリが Flutter による技術の制約を許容することができると判断できたから」です。デメリットよりもメリットの方が大きいと判断した2つの特徴を紹介します。

まず1つ目の特徴として、「Flutter によるアプリ構築におけるデザイン上の制約を受け入れられる」という点がありました。

forSCHOOL アプリは iOSAndroid の標準的なコンポーネントをそのまま利用することは少なく、独自のデザインガイドラインに沿って UI を構築しています。また iOS アプリの利用者数が多いことや、デザイナーの工数や仕様差分を減らすことを考慮して、Android も基本的には iOS のデザインに合わせる方向でデザインされていました。

Flutter で開発する際、プラットフォームごとにデザインを頻繁に切り替える仕様にしてしまうと、UI 部分の共通化から得られる恩恵が少なくなってしまいます。もちろん場合によっては切り替えた方が良いケースはあるものの、基本的には同じ UI で実装できた方が開発スピードが向上します。そのためプロジェクトを始める前に、プロダクトが実現したいことに見合う範囲で UI を共通化することをデザイナーと合意することを進めていました。この合意が取れたため、Flutter による UI 共通化の制約は乗り越えられると判断しました。

ただ Flutter が利用している Material Design2 自体もなかなかに深いデザインシステムなので、苦戦しながら学習中です...。

2点目の特徴として、「プラットフォーム固有の処理が少ない」ことが挙げられます。

forSCHOOL アプリはカメラ機能(QR 読み取り)や通知機能はあるものの、ネイティブSDKを頻繁に利用するようなアプリではありません。ネイティブSDKを利用する際、大半の機能は公式でメンテナンスされているライブラリ3OSS でカバーできると思いますが、ネイティブのコードを書くケースが多いとやはりFlutter を導入するにはハードルが高くなります。

ただネイティブのSDKが提供している API を使いたくなった場合でも利用する手段4が用意されているので、運用している中でどうしても必要なケースが出てきても対応することができます。そのため今後開発する機能がある程度不確定でも、Flutter を導入するのに安心感を与えてくれました。

開発メンバーと会社体制

2つ目の理由は「社内の開発環境の多様性」です。個人的に Flutter によるリプレースに踏み切れた理由として結構大きかったのでは?と感じています。

ネイティブアプリから Flutter アプリに移行するにあたって、ネイティブ文脈から離れることによるエンジニアのキャリア面の不安や、今まで作ってきたものから脱却しなければならないことによるストレスが発生します。そのためもちろん、開発メンバー全員が 100% 賛成というわけではありませんでした。実際、自身のキャリア戦略を考慮したり、能力を存分に発揮したりしたいという思いから、別のチームに異動するメンバーもいました。

しかし逆に考えると、別チームに異動すれば自分のやりたいことを続けられる環境があるというのは、会社の大きな強みだと思います。一つのプロダクトしか存在せず、そのプロダクトの技術的な環境に気が乗らなくなってしまった場合、取れる選択肢は自分を抑えることや転職を検討するなどに限られてしまうかと思います。転職は会社にとっても損失ですしエンジニア的にもハードルが高いです。結局はエンジニアが自分を抑えるケースも多いと思いますが、そのような状況でモチベーションを維持し続けるのも大変です。リプレースのような大きなプロジェクトを成功させるには開発者のモチベーションが不可欠なので、開発者が自分のやりたいことができるチームに所属できる(可能性が高い)会社の環境は大きな要因だと考えています。

当社はたくさんのプロダクトを抱えているため5、各エンジニアが自身のやりたいことをやれる環境が存在する可能性が高く、メンバーのチーム移動も頻繁に行われています。このように各プロダクトに関わるエンジニアのモチベーションを高く維持できる社内の環境は、リプレースのような大きなプロジェクトを成功させる大きな要因となっていると考えています。実際、リプレースプロジェクトに参画したメンバーは皆、新しい技術である Flutter へのモチベーションが高く、とても楽しく開発することができました。

新規開発やリスクを考慮したプロジェクトの進め方

最後の理由として「新規開発やリスクを考慮したプロジェクト計画が立てられたこと」が挙げられます。

リプレースプロジェクトを行うには、当然ながら多大な開発リソースが必要になります。そのため企画やビジネス側の立場からすると、やりたい新規開発や普段の運用への影響は当然気になる点です。また比較的新規な技術である Flutter の技術的なリスクも気になるところでしょう。プロダクト開発には多くの人間が関わっているため、可能な限り懸念を払拭できる方法でプロジェクトを進める必要がありました。

これらの点については、実際にどのようにプロジェクトを進めたのかに繋がるテーマとなるため、以下の「リプレースの進め方」の中で紹介します。

リプレースの進め方: 開発リソース管理とリスク管理

プロジェクトを進めるにあたって重要なことは多々ありますが、ここではリプレースプロジェクトとして特徴的な「開発リソースの管理」と「リスク管理」の2点について紹介します。

開発リソースの管理

forSCHOOL アプリは新規でガンガン機能開発をする必要もあるアプリなので、もちろんリプレースプロジェクト中も新規機能開発や運用を継続しなければなりません。つまり市場でリリースされているネイティブアプリの運用開発も行いながら、リプレースプロジェクトを進める必要がありました。そのため開発運用とリプレースプロジェクトのリソース管理を適切に行わなければなりません。またネイティブアプリ側で開発したものを随時 Flutter 側に取り込みながら作っていく...といったことも必要でした。

我々開発チームは「開発リソースの内3割6を負債解消に利用できる」というルールで開発チーム外との合意がありました。そのためネイティブアプリの負債解消を止めFlutterアプリ開発に3割全てを利用することで、新規開発を止めることなくリプレースプロジェクトを進めることができました。「3割だけだと大変そう」という考えもあるかと思いますが、逆に3割の開発リソースしか注ぐことができないという制約があったため、タスクの優先順位などをスムーズに決められたのではないかなと考えています。利用できるリソースが多かったり不定だったりすると、それはそれで「せっかくのリプレースだからあれもこれもやりたい」という風に工数が膨れがちなので...。

あまりにも期間が長いプロジェクトになってしまうとリスクも増大していくので、プロジェクトの期間はやはり考慮しなければなりません。しかし今回の場合、緻密なタスク分解をして初回リリースに取り込むものを整理した結果、3割のリソースでも1年半程度でタスクを消化することができると計画できました。このようにして「負債解消枠である3割のリソースで、リプレースを1年半の期間で完遂させる」というプロジェクトを始めることができました。

リスク管理

Flutter は2017年に Google によって公開された比較的新しい技術です。急速に人気を集めている一方で、プロダクション環境で利用するにはネイティブ開発と比較してリスクが伴うことも考慮する必要があります。我々のチームでは、Flutter を導入するリスクを段階的に軽減するアプローチを取りました。

まず、一つ目のアプローチは、Flutter が提供する Add-to-app7 を利用し、ネイティブアプリの一部を Flutter で構築したことです。具体的な方法については、当社の別のチームが取り組んだ事例8を参照してみてください。これは、ネイティブアプリに Flutter を組み込むのではなく、ネイティブアプリのソースコードを Flutter プロジェクトとしてビルドする方法です。本記事では技術的な詳細は割愛しますが、段階的に Flutter を組み込む方法は充分に用意されており、小規模なリリースを通じて Flutter の信頼性を確認することが可能9であることが伝わればと思います。

二つ目のアプローチは、まず Android アプリをリプレースし、その後に iOS アプリをリプレースする計画を立てたことです。forSCHOOL では、iOS ユーザーと Android ユーザーの比率が8:2ほどでした。もちろん、Android ユーザーを軽視しているわけではありませんが、全体のリスクを考えるとまずAndroid アプリをリリースしてから iOS アプリをリリースする方が全体への影響を抑えられると判断しました。

Flutter は Google がメインで開発しているため、iOSよりもリスクが低いという点も大きな理由です。また、開発したアプリはもちろんテストが必要ですが、いきなり iOSAndroid 両方でテストを行うと、テスト項目が単純に二倍になり、不具合の原因が特定しにくくなる懸念もありました。まず Android アプリに集中して充分にテストしてからリリースし、次に iOS 用の最終調整を行うことで、iOS アプリの不具合を最小限に抑えることができました。実際にiOS 特有10の不具合が発生したので、このアプローチは適切であったと考えています。

このように、リスク管理を行いつつ新規開発と並行してプロジェクトを進めることができたことで、開発チーム内外ともに安心してプロジェクトを進めることができました。

プロジェクト結果

紆余曲折ありましたが、結果的に当初の計画よりも前倒ししてリリースすることができ、さらに重大な不具合も無しという素晴らしい結果でプロジェクトを完遂することができました!

リプレースした結果

リプレースを終えてみて、実際にどのような結果が得られたのかについて紹介します。 結論から言うと、今のところデメリットという程気になる点はほとんど見当たらず、あらゆる点で大きなメリットを得ることができました。

開発コストの削減

まずプロジェクトの発端ともなった開発コストですが、計画当初はネイティブ開発と比べて 2/3 程度になることを見込んでいました。iOSAndroid の開発が一つにまとまることをシンプルに考えると 1/2 になりますが、やはりそれぞれのプラットフォーム起因の考慮は必要になるので 2/3 程度だろうというイメージです。

しかし蓋を開けてみると、同規模の案件に対して見積もり工数約1/3程度にまで削減されました!

厳密な見積もり方法があったわけではないので、見積もり工数をそのまま数字通り受け取るのは正しくないかもしれないですが、感覚的に大きくズレてはいないかなと思います。

仕様差分の削減

そしてプラットフォーム間の仕様差分のコストも大きく改善されたと感じています。最終的に稼働するプラットフォームが違う以上、iOSAndroidで異なる挙動があることを完全に防ぐことはできないのですが、アプリ開発者が開発するコード部分での仕様差分は原理的になくなります。

例えば各種サーバーとのデータのやりとりの仕方、データをどのように加工して UI として表示しているのかなどを全く同じ方法で行っていることを保証できます。そのため OS 起因の、ある意味仕方ない挙動差分以外の差分は起きないので、大きなメリットを得ることができたと考えています。

ワンチームの恩恵

そのほかにも当初想定していなかった多くのメリットがありました。

まず iOSAndroid の開発チームが統合されてモバイルアプリ開発チームとしてワンチームになり、コミュニケーション量が増えたことです。

以前は iOS 開発チームと Android 開発チームが分かれており、デイリースクラムや Slack チャンネルなども別々でした。扱う技術が違うので仕方ない面もありますが、チームが分かれてしまうと、チーム間のコミュニケーション量は確実に減ってしまいます。同じアプリを作っている以上、連携した方が良いケースは多々あります。しかし、その度にチーム間を跨いだ連絡や会議を行う必要があると、一つ一つは小さくても積もれば大きなオーバーヘッドになってしまいました。

そこで扱う技術が統一され、皆が同じ技術的な文脈を共有できるようになったことで、デイリースクラムや Slack チャンネルは共通化され、不具合の確認や困っていることの相談なども気軽に行えるようになりました。コミュニケーション量が増えたことで、モバイルアプリ開発チームとしての一体感が間違いなく向上していると思います。

また技術スタックが共通化されたことで、全員が全員の PR をレビューすることができる環境にもなりました。この環境によって、自分の PR がレビューされる機会が増え、また自分がレビューする機会も増えます。例えば iOSAndroid アプリでそれぞれ 2 人ずつ開発していて、iOS 開発のメンバーだとすると、PR を見たり見られたりするメンバーは 1 人しかいません。しかし 4 人で Flutter アプリを開発している場合はそれが 3 人になります。

レビューの数が増えればそれだけコード品質も向上しますし、PRをレビューしたり逆にレビューしてもらったりすることは技術的な成長に直結しやすいため、開発メンバーの技術力向上にも大きく貢献します。

さらに全員が全員の PR を見られる環境だと、ソースコードに対して各メンバーが共通認識を持ちやすくなります。そのため案件の見積もりもメンバーによって大きくズレることが少なくなり、精度が向上しているなと感じました。

これらの変化は、中長期的には大きな効用をもたらすものだと確信しています。

このように、当初解決したかった課題に関して大きく改善することができ、かつ想定していなかったメリットもたくさん得ることができました。 早いものでリプレースを終えて半年弱の期間が経過しようとしていますが、このプロダクトにとって Flutter リプレースは大正解だったと感じています。

最後に

forSCHOOL モバイル開発チームでは、Flutter を用いて iOSAndroid アプリのリプレースを行いました。

本記事では、ネイティブ開発を行う際にどのような課題を抱え、なぜリプレースに踏み切ったのか、どのようにプロジェクトを進めたのか、そして結果的にリプレースはどんな効果をもたらしたのかについて紹介しました。昨今 Flutter によるリプレース事例は増えてきていると思いますが、本記事も一つの事例として参考になれば嬉しいです。

今回はリプレースプロジェクトの概要についての紹介でしたが、紹介できなかった詳細な技術的な取り組みについては改めて情報発信していく予定です。


  1. https://teachers.studysapuri.jp/service/guide
  2. https://m3.material.io/
  3. https://pub.dev/publishers/flutter.dev/packages
  4. https://docs.flutter.dev/platform-integration/platform-channels
  5. https://www.recruit.co.jp/service/
  6. 当時開発メンバーが6人いたので、ざっくり2人程度はリプレースプロジェクトに取り組める状態でした
  7. https://docs.flutter.dev/add-to-app
  8. https://speakerdeck.com/recruitengineers/kurosupuratutohuomukai-fa-2022-flutterreact-nativefalsedao-ru-toshi-jian?slide=34
  9. 部分リプレースを行う際は、状態管理の複雑さを軽減するため、ネイティブとFlutter間でできるだけ疎結合に実装できる画面から行うことをお勧めします。我々のチームではまず画面遷移的に末端に位置していて、状態管理も独立している1画面のみを置き換えてリリースしました。基本的には状態管理の区切りがつけられる単位で置き換えていくとより安全だと思います。
  10. プラットフォームごとで処理が異なる通知機能や、iOSのSafeAreaによるレイアウト崩れ、当時iOSのみ適用されていたImpellerというレンダリングエンジンによる不具合など