はじめに
個人開発でFlutterアプリ PayLoop を作り、Android / iOS のストア審査に出せるところまで短期間で進めました。
※ 私はFlutterを1行も書いたことはありません。
この記事では「CodexなどのAgentを使っているのに、なかなかアプリ開発が前に進まない」「短期間でリリースしたいが、指示の出し方が分からない」という人向けに、実際に行った進め方をまとめます。
結論から言うと、効いたのは次の3つです。
- 最初に
AGENTS.mdで制約と作業順序を明文化する - すべての判断と作業を
docs/runlog.mdに残す - 実装前に
docs/spec/docs/design/docs/tasksを作り、Agentに「迷わせない」
Agentにいきなり「アプリを作って」と投げるより、最初に開発ルールと意思決定の置き場を作った方が、最終的には速かったです。
作ったアプリ
作ったのは、繰り返し発生する支出を管理するFlutterアプリです。
例えば、毎月のサブスク、光熱費、日用品、隔週の買い物、定期的な散髪代などを登録して、次の支払日や予定金額を確認できるアプリです。
主な機能は以下です。
- 初回オンボーディング
- 支出項目の作成 / 編集 / 削除 / 一時停止
- 支払い周期の計算
- ホームで次回支払日とカウントダウンを表示
- カレンダー表示
- 支払い予定額の集計
- 通貨設定
- ローカル通知
- AdMob広告
- サブスクリプション前提の有料機能
- 無料ユーザーは4件まで作成可能
- 有料ユーザー向けのラベル、テーマ変更、広告非表示
- App Store / Google Play提出に必要なプライバシーポリシー、利用規約リンク、データ削除ページ
ストア用スクリーンショットも作成しました。
短期開発で最初にやったこと
最初にやったのは、Flutterのコードを書くことではありません。
先に AGENTS.md を作り、Agentに守らせるルールを決めました。
特に重要だったのは、以下のような制約です。
- 実装前に仕様書を書く
- 実装前にデザイン方針を書く
- 実装前にタスクリストを書く
- 判断、変更、意味のあるコマンド実行はRunlogに残す
- 仕様が曖昧なものは「Must Ask」と「Can Assume」に分ける
- 広告、課金、プライバシー、データ削除、通知などは勝手に決めない
- テスト、lint、build確認をしてからコミットする
- コミットは機能単位に分ける
この制約を最初に入れたことで、Agentがその場の勢いでコードを書き散らすことをかなり防げました。
実際の指示イメージはこんな感じです。
まず AGENTS.md を作ってください。
このプロジェクトでは、実装前に docs/spec、docs/design、docs/tasks を作成してください。
判断・変更・意味のあるコマンド実行は docs/runlog.md に記録してください。
広告、課金、プライバシー、通知、データ削除に関わる仕様は Must Ask として扱い、
こちらの確認なしに実装しないでください。
Flutter実装は、仕様・設計・タスクが揃ってから開始してください。
ポイントは「ちゃんと作って」ではなく、「どの順番で、何を根拠に、どこへ記録するか」まで指示することです。
実際の初回セッションでは、ここをかなり細かく書きました。
例えば、AGENTS.md に含める項目として、Project Overview、Agent Roles、Work Flow、Runlog Rule、Commit Rule、Question Rule、Flutter Development Policy、Design Policy、Product Requirements まで指定しています。
さらに、最初の作業も以下に限定しました。
まずは実装に入らず、以下だけを実行してください。
1. AGENTS.md を作成
2. docs/runlog.md を作成
3. docs/spec/product-overview.md を作成
4. docs/spec/mvp-scope.md を作成
5. docs/design/design-principles.md を作成
6. docs/tasks/initial-task-list.md を作成
その後、作成した内容を要約し、
次に実装へ進む前に確認すべき質問を提示してください。
この「実装に入らず」が重要でした。
Agentは実装能力が高いので、すぐコードを書かせたくなります。ただ、最初の時点でルール、仕様、設計、タスクを作らせたことで、その後のセッションでも「何を守るべきか」が残りました。
docsを先に作った理由
今回、実装前に以下のようなドキュメントを作りました。
docs/spec/product-overview.mddocs/spec/mvp-scope.mddocs/spec/technical-policy.mddocs/spec/payment-model-and-recurrence.mddocs/spec/notification-scheduling.mddocs/spec/monetization-and-privacy.mddocs/spec/privacy-policy-draft.mddocs/design/design-principles.mddocs/design/design-system.mddocs/tasks/initial-task-list.md
最終的に、Runlogと仕様・設計・タスク系ドキュメントだけで約3,400行以上になりました。
これだけ見ると「ドキュメントを書きすぎでは?」と思うかもしれません。ただ、Agent開発ではこのドキュメントがかなり効きます。
理由は、Agentが過去の判断を忘れたり、別の作業中に前提を崩したりすることがあるからです。
例えば、今回のアプリでは以下のような判断がありました。
- 無料ユーザーは4件まで支出項目を作成できる
- 有料ユーザーは作成上限なし
- ラベル機能は有料限定
- 通知は前日ON、当日OFFを初期値にする
- 削除した支出項目は復元しない
- 削除したラベルは既存支出から参照を外す
- データはローカル保存中心
- EU配信は初回リリース対象外
- 広告は有料ユーザーには表示しない
これらを口頭のやり取りだけで維持するのは難しいです。
そこで、仕様として固定し、Runlogにも残すようにしました。
Runlogはなぜ必要だったのか
Runlogは、単なる作業メモではありません。
今回の開発では、Runlogを「Agentの外部記憶」として使いました。
記録フォーマットは以下です。
## YYYY-MM-DD HH:mm
Role: Product Owner / Designer / Engineer
Action:
- 何をしたか
Reason:
- なぜしたか
Files:
- 変更した主なファイル
Next:
- 次にやること
この形式にした理由は、あとから読み返したときに「何をしたか」だけでなく「なぜそうしたか」が分かるようにするためです。
Agent開発では、コードの差分だけ見ても判断理由が分からないことがあります。
例えば、Androidのrelease signing設定を追加したとき、単に build.gradle.kts に署名設定を足しただけでは背景が分かりません。Runlogには「Google Play ConsoleでアップロードAABがデバッグ署名と判定されたため」と残しました。
この記録があると、別セッションでAgentに作業を再開させても、次に何を確認すべきかが分かります。
また、今回のようにあとからブログを書くときにも、Runlogがそのまま時系列の材料になります。
実際に使ったプロンプト例
ここからは、実際に効いた指示の型を紹介します。
1. 最初にルールを作らせる
このFlutterアプリを短期間でリリースしたいです。
まず AGENTS.md、docs/runlog.md、docs/spec、docs/design、docs/tasks を作ってください。
実装はまだ開始しないでください。
先にMVPスコープ、Must Ask項目、技術方針、デザイン方針、タスクリストを整理してください。
いきなり実装させないのが大事です。
特に課金、広告、通知、プライバシー、データ削除は、あとから直すとストア審査や設計に大きく影響します。最初に分類させた方が安全です。
2. 仕様が曖昧なところを質問させる
実装前に、Must Ask と Can Assume を分けてください。
Must Ask:
- monetization
- advertising
- privacy
- data deletion
- notification behavior
- core navigation
Can Assume:
- minor UI copy
- internal file organization
- common Flutter implementation choices
Agentに「分からないことがあれば聞いて」とだけ言うと、質問が多すぎたり、逆に重要なところを勝手に決めたりします。
そこで、聞くべきものと仮定してよいものを最初に分けました。
3. 技術方針を先に決めさせる
Flutter実装前に technical-policy.md を作ってください。
以下を必ず含めてください。
- state management
- routing
- local database
- notifications
- AdMob
- subscription
- theme
- localization
- currency
- test strategy
- build and deploy strategy
今回の技術方針では、Riverpod、go_router、Drift/SQLite、flutter_local_notifications、google_mobile_ads、in_app_purchase などを採用する前提を文書化しました。
これを先に決めたことで、あとから画面ごとに状態管理がバラバラになることを避けられました。
4. 実装は小さい単位で進める
次はオンボーディングと支出作成フローだけ実装してください。
実装後に formatter、analyze、test、Android debug build を実行してください。
完了したらRunlogを更新し、論理単位でコミットしてください。
「全部実装して」ではなく、1つのまとまった機能単位で進めました。
今回のコミット履歴も、以下のような粒度に分かれています。
- Flutterプロジェクトのセットアップ
- オンボーディングと作成フロー
- 通知向けAndroid desugaring修正
- 支払い詳細と通貨設定
- MVP支払い機能の完成
- カレンダーUX改善
- テーマ選択
- AdMobユニット設定
- ストア審査前チェックリスト
- Privacy Policy / Termsリンク
- Bundle ID / Application ID設定
- Androidリリース署名設定
- データ削除リクエストページ
この粒度にすると、失敗しても戻しやすく、レビューもしやすいです。
5. ストア審査前はチェックリストを作らせる
App Store / Google Play に提出する前に、審査リスクをチェックしてください。
過去にありがちなリジェクト理由を想定して、docs/tasks/store-release-review-checklist.md を作ってください。
特に以下を確認してください。
- IAPの完成度
- subscription画面に必要な情報
- Privacy Policy / Terms of Use
- AdMob App ID
- Google Play Data safety
- 不要な権限やbackground modes
- metadataの整合性
これはかなり役に立ちました。
実装が動いていても、ストア審査に必要なものは別です。チェックリストを作ったことで、アプリ内のリンク不足、Privacy Policy公開、AdMob App ID、Data safety、データ削除URLなどをリリース前に潰せました。
6. ストア入力で迷ったら、その場で聞く
ストア提出の途中では、実装とは違う細かい判断が何度も発生します。
例えば、実際のセッションでは以下のような質問をしました。
iOSのサブスクのローカリゼーションですが、
表示名と説明はこれで問題ないでしょうか?
App Store Connectで、プロモーション用テキスト、
概要、キーワード、サポートURL、マーケティングURL、
著作権を埋める必要があります。提示してください。
App Store Connectでプライマリカテゴリ、
コンテンツ配信権、アプリプライバシー、
NSUserTrackingUsageDescription の対応が必要と言われています。
どう入力すればよいですか?
Google Play の Data safety で、
データの収集とセキュリティは「はい」でよいですか?
READ_MEDIA_IMAGES 権限をアプリでどのように使用するか説明してください。
このような質問は、コードの実装とは違いますが、短期リリースではかなり重要です。
特に、App Store / Google Playの管理画面は、項目名だけ見ても判断しづらいことがあります。そこで、Agentには「PayLoopの現在の実装と仕様に照らして、どの入力が自然か」を答えさせました。
このときも、Privacy Policy、AdMob、課金、写真アイコン、ローカル保存方針がdocsに残っていたため、回答の一貫性が保ちやすかったです。
ストア用スクリーンショット
Android / iOS のストア用スクリーンショットは、以下のプロジェクトを利用して作成しました。
このテンプレートをベースに、Next.jsのスクリーンショット編集環境を marketing/screenshots 配下に用意し、iOS / Android向けの各サイズを書き出しました。
生成物は screenshots ディレクトリに置いています。
スクリーンショット作成用のセッションでは、最初に以下のように依頼しました。
app-store-screenshotsのスキルを使用して、
PayLoopのApp Store用スクリーンショットを作成したいです。
このアプリは、サブスクリプションや毎月購入しているもの、
毎月支払っているものをメモし、
いつ支払いが行われるのか、毎月いくら使っているのかなどを管理するためのアプリです。
この指示に対して、Agent側では以下のように進みました。
-
app-store-screenshotsスキルの手順を確認 - 既存リポジトリに同種のスクリーンショットプロジェクトがないことを確認
- 新規セットアップとして
marketing/screenshotsにNext.jsテンプレートを配置 - Flutter側の
.gitignoreやREADME.mdがテンプレートで上書きされないよう復元 - PayLoopの表示名、アイコン、日本語/英語対応を確認
- 実キャプチャ差し替え前提で、デッキ構成とコピーを用意
ここで重要だったのは、スクリーンショット生成環境も「アプリ本体の開発物」として扱ったことです。
単発の画像作成で終わらせず、テンプレート、設定、出力画像をリポジトリ内に置くことで、後から文言や画像を差し替えやすくなりました。
ストアスクリーンショットは地味に時間を使います。
アプリ本体の開発と同じリポジトリにスクリーンショット生成環境と出力ファイルを置いたことで、「どの画像を使ったか」「どのサイズを書き出したか」が追いやすくなりました。
iPhone向け、iPad向け、Android向けが一瞬で生成することが出来ます。
やり取りの回数と精度
今回の開発は、少なくとも2つの長いセッションにまたがっています。
1つ目は、AGENTS.md とdocsを整備し、Flutter実装、ストア設定、App Store / Google Play提出直前の相談まで進めたセッションです。
2つ目は、app-store-screenshots を使ってストア用スクリーンショット作成環境を用意したセッションです。
正確な会話往復数はこの記事では集計していませんが、Runlog上の作業チェックポイントは多く、最終的なコミットは初期コミット後に30件ありました。
Runlogは1,100行以上になっています。
ただし、やり取りが多いから非効率だったというより、短い単位で確認と修正を回した結果です。
セッション記録を見返すと、短期リリースで効いたのは「大きな依頼を1回で終わらせる」ことではありませんでした。
むしろ、以下のような短い相談を何度も挟んだことが効いています。
- このストア入力はどう埋めるべきか
- この審査警告は実装変更が必要か、メタデータ入力で解消するのか
- サブスクリプションの商品IDと基本プランIDはどう分けるのか
- AABの署名エラーはどこを直すべきか
- Data safetyでローカル保存データを収集扱いにするべきか
これらは、Agentにコードだけでなく「リリース作業の伴走」をさせた例です。
特に精度が上がったと感じたのは、次の条件が揃ったときです。
- 仕様がdocsにある
- 次にやることがRunlogにある
- テストコマンドが明確
- 変更範囲が1機能に絞られている
- リリース前チェックリストがある
逆に精度が落ちやすいのは、以下のような指示でした。
- 「いい感じにして」
- 「全部作って」
- 「審査通るようにして」
- 「前の続きやって」
こういう指示を出す場合でも、参照すべきdocsやRunlogの範囲を指定するとかなり改善します。
docs/runlog.md の最新の Next を確認して、次の作業だけ進めてください。
変更後は docs/runlog.md を更新してください。
この一文だけでも、Agentが迷走しにくくなります。
指示のコツ
今回やってみて、Agentにアプリ開発を任せるときのコツは以下だと思いました。
1. 役割を分ける
Product Owner、Designer、Engineer の視点を明示しました。
実際に別プロセスを立てたわけではありませんが、Runlog上で役割を分けることで、同じ作業でも視点が整理されます。
例えば、広告を入れる場合でも、Engineer視点ではSDKやApp IDの話になりますが、Product Owner視点では無料/有料の体験差、Designer視点では広告の邪魔さが問題になります。
2. Must Askを先に決める
Agentは便利ですが、勝手に決めてほしくない領域があります。
今回だと以下です。
- 課金
- 広告
- プライバシー
- データ削除
- 通知頻度
- 主要な画面構成
これらは最初にMust Askにしました。
一方で、細かいUI文言やファイル構成はCan Assumeにしました。全部を確認制にすると開発が止まるからです。
3. 「実装して」ではなく「検証までして」
毎回、実装だけでなく検証コマンドまで含めました。
今回よく使った確認は以下です。
dart format lib test
flutter analyze
flutter test --concurrency=1
flutter build apk --debug
flutter build ios --debug --no-codesign
flutter build appbundle --release
もちろん環境やタイミングによって実行できないものもあります。その場合は、Runlogに「なぜ実行できなかったか」と「次に何をするか」を残しました。
4. リリース作業を実装と分ける
アプリが動くことと、ストアに出せることは別です。
今回も、MVPが動いた後に以下のような作業が必要でした。
- Privacy Policy公開
- Terms of Useリンク追加
- AdMob App ID差し替え
- iOS Bundle ID設定
- Android Application ID設定
- Android release signing設定
- Google Play Data safety
- データ削除リクエストURL
- ストア用スクリーンショット
- App Store / Google Playの課金商品設定
短期リリースを狙うなら、実装完了後にこれらを始めるのでは遅いです。
仕様段階から、ストア審査で必要になるものをdocsに入れておくのが良いです。
苦戦したこと
image_pickerのChannel Error
支出アイコンに写真を設定できるようにしたとき、Android実機で PlatformException(channel-error) や MissingPluginException が出ました。
原因は、プラグイン追加後にhot reload / hot restartのまま動かしていて、native plugin channelが登録されていなかったことです。
対応として、例外を捕捉し、クラッシュせず「写真選択を使うにはアプリを再ビルドしてください」というSnackBarを出すようにしました。
カレンダーのスワイプ感
カレンダーUIは何度も調整しました。
最初は AnimatedSwitcher で月表示を切り替えていましたが、実際に触るとGoogle Calendarのように隣の月が見えながらスライドする方が自然でした。
最終的には PageView ベースにし、スワイプのしきい値も複数回調整しました。
Runlogを見ると、18%、10%、4%と段階的に調整しており、こういう体感系の改善こそRunlogが役に立ちました。
flutter_local_notificationsのAndroid desugaring
Android debug buildで、flutter_local_notifications に必要なcore library desugaring設定が不足していました。
これは android/app/build.gradle.kts に desugaring を追加して解決しました。
AdMob App IDと広告ユニットIDの違い
AdMobでは、App IDと広告ユニットIDが別です。
公開記事では具体値は出しませんが、形式としては以下のような値です。
AdMob App ID: ca-app-pub-0000000000000000~0000000000
Ad Unit ID: ca-app-pub-0000000000000000/0000000000
広告ユニットIDだけ設定しても、AndroidManifestやInfo.plistにApp IDが正しく入っていないと起動時や審査前確認で問題になります。
Android release signing
Google Play ConsoleにAABをアップロードしたとき、debug keyで署名されていると判定されました。
対応として、android/key.properties をローカルに置き、release build時だけ署名情報を読むようにしました。
key.properties 自体はgitに入れず、key.properties.example だけを管理しました。
Google Playのデータ削除URL
PayLoopはアカウントなし・ローカル保存中心のアプリですが、Google PlayのData safetyではデータ削除リクエストURLが必要になりました。
そのため、公開サイト側にデータ削除ページを作り、アプリ内削除とアンインストールでローカルデータを削除できることを説明しました。
このような「アプリの機能ではないが、リリースには必要なもの」は見落としやすいです。
ストア提出画面の細かい入力
App Store Connectでは、アプリのカテゴリ、コンテンツ配信権、プライバシーポリシーURL、アプリプライバシー、審査メモ、サブスクリプション説明などを埋める必要がありました。
Google Play Consoleでも、Data safety、READ_MEDIA_IMAGES権限の用途説明、定期購入アイテムID、基本プランID、クローズドテストなどの入力が必要でした。
ここは、実装よりも「今のアプリ仕様を正しく説明する」作業です。
Agentにストア入力文を出させるときは、以下のように聞くと精度が上がりました。
現在のPayLoopの実装とdocs/specの内容に合わせて、
ストア審査で誤解されにくい文面を提示してください。
秘匿情報や個人情報は含めないでください。
単に「文面を作って」ではなく、「現在の実装とdocsに合わせて」「審査で誤解されにくい」と条件を付けるのがポイントです。
秘匿情報の扱い
この記事では、以下の情報は公開しないか、ダミー値に置き換えています。
- AdMob App ID
- 広告ユニットID
- 電話番号
- 個人名
- 個人の連絡先
- keystoreパスワード
- ストアアカウントに紐づく識別子
- セッションIDの完全な値
例として、ID類は以下のようにマスクします。
ca-app-pub-0000000000000000~0000000000
ca-app-pub-0000000000000000/0000000000
019ec1df-****-****-****-************
Agentにブログを書かせる場合も、最初に「秘匿情報はマスクする」と明示しておくのが重要です。
まとめ
Codexを使って短期間でアプリを作るには、Agentに自由に作らせるより、最初に開発の枠組みを作った方が速いです。
今回特に効いたのは以下です。
-
AGENTS.mdで作業順序と制約を固定する -
docs/runlog.mdをAgentの外部記憶として使う - 仕様、設計、タスクを先に作る
- Must AskとCan Assumeを分ける
- 実装単位を小さくする
- 実装後に検証コマンドまで実行させる
- ストア審査用チェックリストを早めに作る
- スクリーンショット生成もリポジトリ内で管理する
Agent開発では、プロンプトのうまさだけでなく「Agentが参照できる状態」をどれだけ用意できるかが重要だと感じました。
この記事自体も、今回の docs/runlog.md とセッション記録を元に作成しています。
つまり、Runlogは開発を進めるためだけでなく、あとから「どうやって作ったのか」を振り返り、記事やリリースノートに変換するための素材にもなります。
