「メディカルサークル」は、医学部生のノートや講義資料を整理・共有できるモバイルアプリです。
iOS / Android の両プラットフォームを 1 つのチームで素早く形にする必要があり、技術スタックの選定は開発初期の最重要テーマでした。
本記事では、Flutter を中心としたこのアプリの構成と、それぞれの技術を選んだ理由を整理します。
目次
iOS / Android を 1 コードベースに揃えるための Flutter
Flutter を選んだ最大の理由は、iOS と Android を 1 つのコードベースで開発しながら、ネイティブに近いパフォーマンスと UI 表現を維持できることでした。
Firebase 連携や RevenueCat によるサブスク管理も共通コードで完結するため、ネイティブ 2 本立てに比べて開発工数は体感で半分以下に収まっています。
医学部生向けという比較的ニッチな市場では、まず両OSで動かすこと自体が価値になりました。
Riverpod × go_router で状態と画面遷移を宣言的にまとめる
状態管理には Riverpod、ルーティングには go_router を採用しました。
go_router の ShellRoute でボトムナビゲーション付きの画面構成を宣言的に定義し、Riverpod で認証状態やサブスク状態をグローバルに監視しています。
「未ログインならログイン画面にリダイレクト、ゲストなら機能制限付きで通す」といった分岐を、ルーターのリダイレクトと Provider の組み合わせで実現でき、画面側にロジックを持たせずに済みました。
Firebase を選び、インフラ構築をゼロにする
バックエンドは Auth / Firestore / Storage / Functions / Messaging をフルセットで利用しています。
ワンエコシステムで完結するため、リアルタイム同期やファイル配信、プッシュ通知までインフラ構築ゼロで動かせる状態を初日から作れました。
一方で、セキュリティルールには工夫が必要でした。
「匿名は非公開のみ」「有料会員だけコメント可」のようなビジネスロジックをルールで表現すると条件が複雑化し、テスト・保守コストが想定以上にかかります。
そのため、ルールはユニットテストに近い形で検証できる体制を整えました。
features ベースの構成で機能追加を素早く保つ
アプリは features/ 配下を画面単位(auth / timeline / upload / chat など)で分割するフィーチャーファースト構成にしています。
各 feature 内に screen と widgets を閉じ込め、依存する共通 UI(AppDialog / SectionHeader / UserAvatar など)は早い段階でコンポーネント化しました。
この形を守ったことで、後発の画面は組み合わせるだけで作れる状態を維持できています。
ブランド情報を 1 ファイルに集約する
アプリ名やプライマリカラー、有料プラン名といったブランド情報はすべて brand.dart に集約しています。
実際、リリース直前に仮称から「メディカルサークル」へ、有料プランも「Premium」から「メディカルサークル Pro」へと改名がありました。
このとき brand.dart の定数 2 行を書き換えるだけで、UI 全画面・ストア提出文言・管理画面の表示名まで一括で追従でき、リリーススケジュールへの影響をゼロに抑えられました。
まとめ
メディカルサークルは「個人開発に近い柔軟さで、製品としての完成度を出す」ことを前提に技術スタックを選びました。
Flutter で 2 OS を抱え、Firebase でインフラを持たず、Riverpod と go_router で状態と遷移を宣言的に組む。
この組み合わせは、ニッチ市場向けの中規模アプリで再現性が高い構成だと感じています。

.webp%3Falt%3Dmedia%26token%3D6ca2c2ef-9413-4453-b992-55b66b11ed54&w=3840&q=75)


.webp%3Falt%3Dmedia%26token%3D900f385d-12a2-449b-8d1e-83a57cef0088&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D0e802fb0-2dda-44a7-bf80-5d39019635ba&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D3fb3dc66-ecca-402e-8fb8-fbec9407f7f5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Ddb21d760-e1ed-4ec2-af28-3462041e31b5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dcce7bd72-f11e-4292-86bf-e6ccf3e7bf32&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D457ff920-e0df-4ff5-95eb-e29f74b73823&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dc21fcc77-7404-458d-9eb5-85b8d84ae1bc&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D92052f12-5280-49df-877a-b514582e95db&w=3840&q=75)

.webp%3Falt%3Dmedia%26token%3Da7c14698-1b08-4fea-89c6-f77a9121f4c5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D899eeefd-f4c9-44a6-9ec2-3ced0b223ffd&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dca25fa6b-e233-43f7-90c3-e68e4c5b0bc5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D7f18e5f1-cfda-4148-ab86-b3d2e6547262&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D5f10e078-4d87-4c87-928c-21b719cbf1cb&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D957b18b6-9b01-4c94-9207-7b9fca22a787&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dd952e11d-4461-47ae-892d-622fc3f2a48a&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D532bb657-5670-49b4-9165-5f758062d8dd&w=3840&q=75)