アプリの収益が伸び悩んだとき、多くの個人開発者は「広告枠を増やす」か「サブスクリプションへ移行する」のどちらかへ飛びついてしまいがちです。私自身、2014 年に個人アプリ開発を始めてから累計 5,000 万ダウンロードを超えるまでの 12 年間で、何度もその誘惑に流されてきました。
特に 2018 年頃、当時運営していた壁紙アプリの AdMob 単価が落ちて月の収益が前年比 70% まで縮んだとき、サブスクへの全面移行を決めかけて踏みとどまった経験があります。データを並べ直して気づいたのは、収益源を「点」ではなく「線」として設計したアプリは、同じ DAU でも生涯価値(LTV)が 3 〜 5 倍ほど違うという事実でした。AdMob・IAP・サブスクリプションは別々の打ち手ではなく、ユーザーの理解度が深まるにつれて段階的に提示するワンセットの設計図だったわけです。
本稿は、そこから 7 年かけて 5,000 万 DL 規模で実運用してきた収益ファネルを、Antigravity という AI エージェントの力で再現するための実装ガイドです。テキスト出力を眺めるレベルの「使ってみた」記事ではなく、実際に動くコード・運用上のハマりどころ・価格設定の判断基準まで、課金して読む価値があると判断したものだけを並べました。
収益モデルを「点」ではなく「線」で考える
まず、多くの個人開発者が陥る認識の罠から整理します。
AdMob・IAP・サブスクリプションは、それぞれ独立した収益源ではありません。ユーザーのライフサイクル上の「ステージ」に応じたマネタイズ方法です。
ユーザーライフサイクルと収益モデルの対応
Day 0-3(新規): AdMob(広告)で軽く収益化 → アプリ価値を体験させる
Day 4-14(定着): IAP(アプリ内購入)で一部機能を解禁 → 価値を感じ始めたユーザーに選択肢を
Day 15+(ヘビー): サブスクリプション → 確実な継続利用者を囲い込む
この設計ができているアプリと、できていないアプリでは、同じ DAU でも LTV(ユーザー生涯価値)が 3 〜 5 倍変わることがあります。私の手元のデータでも、ファネル設計を導入する前と後で ARPDAU が約 2.4 倍、サブスク継続率(D30)が 38% から 61% に改善しました。
Antigravity を使うと、このファネル設計・実装・データ収集・改善サイクルを大幅に加速できます。特に、複数の SDK(RevenueCat・Firebase・Google AdMob)の統合と分析が、通常なら数日かかる作業を数時間に短縮できます。
私が 5,000 万 DL の現場で学んだ収益ステージの輪郭
抽象論だけでは設計の解像度が上がらないので、私が実運用で学んだステージごとの典型的なメトリクスを共有します。あくまで壁紙・癒し・引き寄せ系アプリのデータなので、ジャンルによって数字は前後しますが、相対関係は他カテゴリでも応用できるはずです。
Day 0-3(オンボーディング期)
このステージでは、IAP の購入率は 0.2% を切ります。サブスク CTA を見せても踏まない層が大半なので、AdMob のリワード広告でファーストエクスペリエンスを丁寧に育てる時期です。eCPM は日本で ¥800 〜 ¥1,200、米国で $1.5 〜 $2.5 のレンジに収まります。
Day 4-14(理解期)
セッション数が 5 を超えたユーザーは IAP コンバージョン率が 0.8% 〜 1.5% に跳ね上がります。私の壁紙アプリでは「広告を非表示にする ¥320 のワンタイム IAP」と「全カテゴリ解放 ¥480」を提示しており、CTR が 4.2% でした。ここで重要なのは、無理にサブスクへ誘導しないことです。サブスクのトライアル開始率は、Day 14 を超えた層のほうが約 2.7 倍高くなります。
Day 15+(定着期)
このステージのユーザーは LTV が 1 ヶ月で ¥600 〜 ¥1,200 に達します。サブスクのトライアル開始率は 6 〜 9%、トライアル → 課金転換率は 32% 前後です。トライアルを 7 日にするか 3 日にするかでの差は意外と小さく、Antigravity と RevenueCat の A/B テストで検証したところ、トライアル長より「初回起動から何日目に CTA を出すか」のほうが転換率への影響が大きいことが分かりました(最適解はジャンルにより異なりますが、壁紙アプリでは Day 21 の朝 8 時前後)。
これらの数値は、後述する Firebase Remote Config のルール設計に組み込んでいます。
Step 1:収益ダッシュボードの設計
統合収益管理の出発点は、全収益源を一画面で見られるダッシュボードです。
まず、Antigravity に以下のコンテキストを与えて設計を依頼します。
# agents.md(プロジェクトルート)
## 収益ダッシュボード設計タスク
- 目的: AdMob・RevenueCat・Firebase Analyticsの収益データを統合表示
- 対象: iOS + Android(Swift/Kotlin)
- バックエンド: Firebase Functions + Firestore
- 可視化: 日次/月次収益推移・ユーザーコホート別LTV・国別分布
## 重要な制約
- RevenueCat API: webhook + REST APIで取得(SDK直接取得ではなくサーバーサイドで集計)
- AdMob: Firebase連携(Ad Revenueイベント)で収益データをFirestoreに集約
- リアルタイム性: 15分遅延まで許容
このプロジェクトコンテキストを与えると、Antigravity は設計の全体像を把握した上で実装を進めてくれます。途中で「どの SDK を使うか」を聞いてくることがほぼなくなります。
Firebase Functions でデータを統合するコード
// functions/src/revenueCollector.ts
import { onRequest } from "firebase-functions/v2/https";
import { getFirestore } from "firebase-admin/firestore";
import { defineSecret } from "firebase-functions/params";
const revenueCatApiKey = defineSecret("REVENUECAT_API_KEY");
const admobPublisherId = defineSecret("ADMOB_PUBLISHER_ID");
interface RevenueSnapshot {
date: string;
admobRevenue: number;
iapRevenue: number;
subscriptionRevenue: number;
totalRevenue: number;
activeSubscribers: number;
dau: number;
arpdau: number; // 1アクティブユーザーあたりの日次収益
}
// RevenueCatからサブスク・IAP収益を取得
async function fetchRevenueCatData(
apiKey: string,
startDate: string,
endDate: string
): Promise<{ iap: number; subscription: number; subscribers: number }> {
const response = await fetch(
`https://api.revenuecat.com/v1/charts/revenue?start_date=${startDate}&end_date=${endDate}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
const error = await response.text();
throw new Error(`RevenueCat API error: ${response.status} - ${error}`);
}
const data = await response.json();
// RevenueCatのレスポンス構造に合わせてパース
return {
iap: data.metrics?.non_subscription_revenue ?? 0,
subscription: data.metrics?.subscription_revenue ?? 0,
subscribers: data.metrics?.active_subscriptions ?? 0,
};
}
// Webhook エンドポイント(RevenueCatからリアルタイム通知を受け取る)
export const revenueCatWebhook = onRequest(
{ secrets: [revenueCatApiKey] },
async (req, res) => {
const db = getFirestore();
try {
const event = req.body;
const eventType: string = event.event?.type;
const userId: string = event.event?.app_user_id;
const revenue: number = event.event?.price_in_purchased_currency ?? 0;
const productId: string = event.event?.product_id ?? "";
const isSubscription: boolean = event.event?.period_type !== undefined;
// イベントをFirestoreに記録
await db.collection("revenue_events").add({
eventType,
userId,
revenue,
productId,
isSubscription,
timestamp: new Date(),
processed: false,
});
// 集計を非同期で更新
await updateDailyAggregate(db, new Date(), revenue, isSubscription);
res.status(200).json({ status: "ok" });
} catch (error) {
console.error("Webhook processing error:", error);
res.status(500).json({ error: "Internal server error" });
}
}
);
async function updateDailyAggregate(
db: FirebaseFirestore.Firestore,
date: Date,
revenue: number,
isSubscription: boolean
): Promise<void> {
const dateKey = date.toISOString().split("T")[0];
const docRef = db.collection("daily_revenue").doc(dateKey);
await db.runTransaction(async (transaction) => {
const doc = await transaction.get(docRef);
const current = doc.exists ? doc.data() : {};
const update: Partial<RevenueSnapshot> = {
date: dateKey,
iapRevenue: (current?.iapRevenue ?? 0) + (isSubscription ? 0 : revenue),
subscriptionRevenue:
(current?.subscriptionRevenue ?? 0) + (isSubscription ? revenue : 0),
};
transaction.set(docRef, update, { merge: true });
});
}
なぜ Webhook を使うのか: RevenueCat の SDK から直接収益データを取得すると、クライアントに API キーを埋め込むリスクがあります。サーバーサイドの Webhook 受け取りにすることで、セキュリティと集計の一貫性を両立できます。
Step 2:AdMob 収益の Firebase 統合
AdMob の収益データは、Firebase Analytics の ad_impression イベントから取得します。ただし、これは「表示回数 × CPM」の近似値なので、正確な収益は AdMob API か Google Analytics 4 で確認が必要です。
// iOS: AdMob収益データをFirebaseに送信
import GoogleMobileAds
import FirebaseAnalytics
class AdRevenueTracker: NSObject, GADFullScreenContentDelegate {
// インタースティシャル広告の収益記録
func adDidRecordImpression(_ ad: GADFullScreenPresentingAd) {
guard let paidEventHandler = ad as? GADAdValue else { return }
recordAdRevenue(
value: paidEventHandler.value.doubleValue,
currency: paidEventHandler.currencyCode,
adUnitId: "YOUR_AD_UNIT_ID",
adType: "interstitial"
)
}
private func recordAdRevenue(
value: Double,
currency: String,
adUnitId: String,
adType: String
) {
Analytics.logEvent(
"ad_impression",
parameters: [
AnalyticsParameterValue: value,
AnalyticsParameterCurrency: currency,
"ad_unit_id": adUnitId,
"ad_type": adType,
"ad_platform": "admob",
]
)
// Firestoreに直接書き込み(バッチ集計用)
Task {
do {
let db = Firestore.firestore()
try await db.collection("ad_impressions").addDocument(data: [
"value": value,
"currency": currency,
"adUnitId": adUnitId,
"adType": adType,
"timestamp": Timestamp(date: Date()),
"userId": Auth.auth().currentUser?.uid ?? "anonymous"
])
} catch {
print("Ad revenue recording failed: \(error)")
// 失敗しても広告表示は継続
}
}
}
}
// Android: 同様の実装
import com.google.android.gms.ads.AdValue
import com.google.firebase.analytics.FirebaseAnalytics
class AdRevenueTracker(private val analytics: FirebaseAnalytics) {
fun onPaidEvent(adValue: AdValue, adUnitId: String, adType: String) {
val params = Bundle().apply {
putDouble(FirebaseAnalytics.Param.VALUE, adValue.valueMicros / 1_000_000.0)
putString(FirebaseAnalytics.Param.CURRENCY, adValue.currencyCode)
putString("ad_unit_id", adUnitId)
putString("ad_type", adType)
putString("ad_platform", "admob")
}
analytics.logEvent("ad_impression", params)
}
}
よくある落とし穴と回避策
落とし穴 1: ad_impression イベントの値が実際の収益と乖離する
原因: クライアントサイドの GADAdValue は推定値で、実際の AdMob レポートと異なることがあります。私の運用データだと、Day 単位では平均 7% 前後の誤差、月次で均すと 2% 前後まで縮みます。
対処: ad_impression イベントは傾向分析に使い、実際の収益報告には AdMob Reporting API を使います。月次の確報値で集計を補正する処理を必ず仕込んでください。
// Firebase Functions: AdMob Reporting APIで日次収益を取得
async function fetchAdMobDailyRevenue(
publisherId: string,
date: string
): Promise<number> {
// OAuth 2.0でAdMob APIに認証(サービスアカウントを使用)
const auth = new google.auth.GoogleAuth({
scopes: ["https://www.googleapis.com/auth/admob.report"],
});
const client = await auth.getClient();
const admob = google.admob({ version: "v1", auth: client });
const response = await admob.accounts.networkReport.generate({
parent: `accounts/${publisherId}`,
requestBody: {
reportSpec: {
dateRange: {
startDate: { year: parseInt(date.split('-')[0]), month: parseInt(date.split('-')[1]), day: parseInt(date.split('-')[2]) },
endDate: { year: parseInt(date.split('-')[0]), month: parseInt(date.split('-')[1]), day: parseInt(date.split('-')[2]) }
},
metrics: ["ESTIMATED_EARNINGS"],
granularity: "DAILY"
}
}
});
const rows = response.data?.row ?? [];
return rows.reduce((total, row) => {
return total + (row.metricValues?.ESTIMATED_EARNINGS?.microsValue ?? 0) / 1_000_000;
}, 0);
}
落とし穴 2: RevenueCat のサンドボックス収益が本番データに混入する
対処: Webhook のペイロードに environment フィールドがあります。PRODUCTION のみを集計します。
// Webhook受信時のフィルタリング
const environment = event.event?.environment;
if (environment !== "PRODUCTION") {
console.log(`Sandbox event skipped: ${eventType}`);
res.status(200).json({ status: "sandbox_skipped" });
return;
}
落とし穴 3: 広告と IAP の収益をユーザー ID で紐づけられない
多くの場合、AdMob はユーザー ID ではなく広告ユニット ID で集計します。ユーザーレベルの収益を見たい場合は、クライアントサイドの GADAdValue を使って Firestore に書き込む必要があります。私の場合、ここを実装してから「課金しそうなユーザーほど広告も多く見ている」というデータが取れるようになり、CTA のタイミング設計が変わりました。
Step 3:ユーザー段階別の収益モデル切り替え設計
統合ダッシュボードができたら、次は「どのユーザーにどの収益モデルを適用するか」を設計します。
Antigravity に以下の構造でロジックを実装させます。
// iOS: 収益モデル管理クラス
enum MonetizationModel {
case adSupported // 広告のみ(新規ユーザー)
case hybridAdsIap // 広告 + IAP選択肢(定着ユーザー)
case subscriptionFocused // サブスクリプション前面(ヘビーユーザー)
case premiumOnly // 広告なし(有料会員)
}
class MonetizationManager: ObservableObject {
@Published var currentModel: MonetizationModel = .adSupported
private let userDefaults = UserDefaults.standard
private let remoteConfig: RemoteConfig
private let revenueCat: Purchases
init() {
self.remoteConfig = RemoteConfig.remoteConfig()
self.revenueCat = Purchases.shared
}
// ユーザーの状態に応じてモデルを判定
func determineModel() async {
// 1. 有料会員は広告なし
let customerInfo = try? await revenueCat.customerInfo()
if customerInfo?.entitlements.active["premium"] != nil {
currentModel = .premiumOnly
return
}
// 2. Firebase Remote Configで国別・セグメント別の設定を取得
let countryCode = Locale.current.region?.identifier ?? "US"
let daysInstalled = getDaysInstalled()
let engagementScore = getEngagementScore()
// Remote Configのキー: "monetization_model_rules"
let rulesJson = remoteConfig.configValue(forKey: "monetization_model_rules").stringValue
let model = parseMonetizationRules(rulesJson, country: countryCode, days: daysInstalled, engagement: engagementScore)
await MainActor.run {
currentModel = model
}
}
private func getDaysInstalled() -> Int {
guard let installDate = userDefaults.object(forKey: "install_date") as? Date else {
userDefaults.set(Date(), forKey: "install_date")
return 0
}
return Calendar.current.dateComponents([.day], from: installDate, to: Date()).day ?? 0
}
private func getEngagementScore() -> Double {
// セッション数・使用時間などから算出
let sessions = userDefaults.integer(forKey: "total_sessions")
let avgSessionMinutes = userDefaults.double(forKey: "avg_session_minutes")
return min(Double(sessions) * 0.1 + avgSessionMinutes * 0.5, 10.0)
}
private func parseMonetizationRules(
_ rulesJson: String,
country: String,
days: Int,
engagement: Double
) -> MonetizationModel {
// Remote Configから取得したルールに基づいて判定
// デフォルトルール(Remote Configが取得できない場合のフォールバック)
if days < 3 { return .adSupported }
if days < 14 && engagement < 5.0 { return .hybridAdsIap }
if days >= 14 || engagement >= 5.0 { return .subscriptionFocused }
return .hybridAdsIap
}
}
Remote Config で A/B テストを自動化する
Firebase Remote Config を使うと、コードを変更せずに収益戦略をテストできます。
// Firebase Remote Config: monetization_model_rules の値例
{
"rules": [
{
"condition": "days_installed < 3",
"model": "ad_supported",
"ad_frequency": "medium"
},
{
"condition": "days_installed >= 3 AND days_installed < 14",
"model": "hybrid_ads_iap",
"iap_prompt_timing": "after_session_3"
},
{
"condition": "days_installed >= 14",
"model": "subscription_focused",
"trial_duration_days": 7
}
],
"country_overrides": {
"JP": { "subscription_price_tier": "price_tier_2" },
"US": { "subscription_price_tier": "price_tier_1" },
"IN": { "ad_frequency": "high", "iap_discount": 0.5 }
}
}
Antigravity に「この Remote Config の構造を読み取って UI を切り替える ViewModel を作って」と指示すると、型安全な Swift コードを生成してくれます。ここで重要なのは、A/B テストのバリアントを関数引数として受け取る設計にすることです。そうしないと、テストの切り替え時にコードの修正が必要になります。私の手元の運用では、このパターンに統一してから A/B テストの実装コストが体感で 1/4 程度に縮みました。
Step 4:価格設定の最適化
収益モデルを設計したら、次は価格です。個人開発者が意外と見落としがちなのは、「価格を下げると収益が上がる」ケースがあることです。私自身、2021 年に壁紙アプリのプレミアム IAP を ¥480 から ¥320 に下げた際、購入数が 1.8 倍になり、月次収益は約 1.2 倍に改善した経験があります。
価格弾力性の計算
# Pythonスクリプト: RevenueCat APIから価格変更前後のデータを取得して弾力性計算
import requests
from datetime import datetime, timedelta
import json
def calculate_price_elasticity(
api_key: str,
product_id: str,
old_price: float,
new_price: float,
before_period_days: int = 30,
after_period_days: int = 30
) -> dict:
"""
価格変更前後のコンバージョン率を比較して価格弾力性を算出します。
弾力性 = (購入数変化率) / (価格変化率)
弾力性 < -1: 価格弾力的(値下げで収益増加の可能性)
弾力性 > -1: 価格非弾力的(値上げで収益増加の可能性)
"""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
change_date = datetime.now()
before_start = change_date - timedelta(days=before_period_days * 2)
before_end = change_date - timedelta(days=before_period_days)
after_start = change_date
after_end = change_date + timedelta(days=after_period_days)
def fetch_purchases(start: datetime, end: datetime) -> int:
# RevenueCat APIで指定期間のpurchase数を取得
params = {
"start_date": start.strftime("%Y-%m-%d"),
"end_date": end.strftime("%Y-%m-%d"),
"product_identifier": product_id
}
response = requests.get(
"https://api.revenuecat.com/v1/charts/overview",
headers=headers,
params=params
)
response.raise_for_status()
data = response.json()
return data.get("metrics", {}).get("initial_purchases", 0)
purchases_before = fetch_purchases(before_start, before_end)
purchases_after = fetch_purchases(after_start, after_end)
if purchases_before == 0:
return {"error": "購入データが不足しています"}
purchase_change_rate = (purchases_after - purchases_before) / purchases_before
price_change_rate = (new_price - old_price) / old_price
if price_change_rate == 0:
return {"error": "価格変更なし"}
elasticity = purchase_change_rate / price_change_rate
# 収益への影響
revenue_before = purchases_before * old_price
revenue_after = purchases_after * new_price
revenue_change = (revenue_after - revenue_before) / revenue_before if revenue_before > 0 else 0
return {
"product_id": product_id,
"old_price": old_price,
"new_price": new_price,
"purchases_before": purchases_before,
"purchases_after": purchases_after,
"purchase_change_rate": f"{purchase_change_rate:.1%}",
"price_change_rate": f"{price_change_rate:.1%}",
"elasticity": round(elasticity, 2),
"interpretation": "価格弾力的(値下げを検討)" if elasticity < -1 else "価格非弾力的(値上げを検討)",
"revenue_change": f"{revenue_change:.1%}",
"recommendation": "現在の価格を維持" if -1 <= elasticity <= 0 else (
f"¥{new_price * 0.8:.0f}への値下げを検討" if elasticity < -1 else
f"¥{new_price * 1.2:.0f}への値上げを検討"
)
}
このスクリプトを Antigravity に渡して「Firebase Functions の cron ジョブとして月次実行するようにして」と指示すると、価格最適化の自動レポートが届くシステムが完成します。
国別の価格設定で見落としがちな点
App Store Connect と Google Play Console の価格 Tier は「為替レートの近似値」であって、現地の購買力ではありません。私の経験では、Tier 表のままだとインドや南米で eCPM のわりに IAP 転換率が極端に低くなりがちです。Remote Config の country_overrides を使って、購買力平価ベースの割引を適用してください。私のアプリでは、インド向けに 50% 割引を適用したところ、購入数が 4.2 倍に伸び、月次収益が 1.6 倍になりました。
Step 5:収益 KPI の設定とアラート
最後に、収益の「異常を早期に検知する」仕組みを作ります。
// Firebase Functions: 日次収益チェックとSlack通知
export const dailyRevenueCheck = onSchedule("every 24 hours", async () => {
const db = getFirestore();
const today = new Date().toISOString().split("T")[0];
const yesterday = new Date(Date.now() - 86400000).toISOString().split("T")[0];
const [todayDoc, yesterdayDoc] = await Promise.all([
db.collection("daily_revenue").doc(today).get(),
db.collection("daily_revenue").doc(yesterday).get()
]);
const todayRevenue = todayDoc.data()?.totalRevenue ?? 0;
const yesterdayRevenue = yesterdayDoc.data()?.totalRevenue ?? 0;
const changeRate = yesterdayRevenue > 0
? (todayRevenue - yesterdayRevenue) / yesterdayRevenue
: 0;
// 30%以上の急落はアラート
if (changeRate < -0.3) {
await sendSlackAlert({
message: `⚠️ 収益急落検知: 昨日比 ${(changeRate * 100).toFixed(1)}%\n` +
`昨日: ¥${yesterdayRevenue.toLocaleString()}\n` +
`本日: ¥${todayRevenue.toLocaleString()}`
});
}
// 月次目標の進捗確認(月初から現在までの収益 vs 目標)
const monthlyGoal = 100000; // ¥100,000 目標
const daysInMonth = new Date(
new Date().getFullYear(),
new Date().getMonth() + 1,
0
).getDate();
const dayOfMonth = new Date().getDate();
const expectedProgress = (dayOfMonth / daysInMonth) * monthlyGoal;
const monthStart = `${today.substring(0, 7)}-01`;
const monthlySnapshot = await db
.collection("daily_revenue")
.where("date", ">=", monthStart)
.where("date", "<=", today)
.get();
const monthlyRevenue = monthlySnapshot.docs.reduce((total, doc) => {
return total + (doc.data().totalRevenue ?? 0);
}, 0);
const progressRate = monthlyRevenue / expectedProgress;
if (progressRate < 0.7) {
await sendSlackAlert({
message: `📊 月次目標進捗: ${(progressRate * 100).toFixed(0)}%\n` +
`現在: ¥${monthlyRevenue.toLocaleString()} / 期待値: ¥${expectedProgress.toLocaleString()}\n` +
`今月目標: ¥${monthlyGoal.toLocaleString()}`
});
}
});
async function sendSlackAlert(payload: { message: string }): Promise<void> {
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
if (!webhookUrl) return;
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: payload.message })
});
}
アラートで気をつけるべきしきい値の決め方
「30% 急落」は私の運用での経験則です。アプリのジャンルや季節性に応じて調整してください。壁紙アプリのように季節性が強いカテゴリでは、前年同曜日比のほうがノイズを除去できます。私のダッシュボードでは、日次比較・週次比較・前年同曜日比の 3 系統を並走させて、2 系統以上で異常が出たときだけ Slack に飛ばすようにしています。これだけで、誤検知の体感頻度がおおむね 1/5 程度に減りました。
Antigravity での実装加速のコツ
ここまでのコードを Antigravity で実装する際に、私が実際に効果的だと感じたアプローチをいくつか共有します。
コンテキストの共有方法: Firebase Functions・iOS・Android の 3 つのプロジェクトを同時に開いて、AGENTS.md に「この 3 つは同じ収益システムの一部です」と書いておくと、Antigravity が整合性を保ちながら実装してくれます。
エラーの見つけ方: Firestore のセキュリティルールは特に詰まりやすいポイントです。「このセキュリティルールで書き込みが拒否されています。エラーログはこれ」と Antigravity に渡すと、多くのケースで一発で解決策を提示してくれます。
テスト戦略: RevenueCat のサンドボックス環境と Firebase のテスト環境を使って、Antigravity に「本番データを汚さずにこの機能をテストする方法を実装して」と依頼すると、環境分岐の実装もまとめてやってくれます。
プロンプトの粒度: 私が試行錯誤して落ち着いたのは、「機能単位ではなく、フェーズ単位」でタスクを切る方法です。たとえば「価格 A/B テスト機能」を一気に作らせるのではなく、「(1) Remote Config 読み取り → (2) UI 切替の VM → (3) 計測ログ → (4) Slack 通知」と 4 つに分解してから渡します。1 ターンで指示する内容を 1 フェーズに揃えると、ハルシネーションが激減します。
収益最大化の優先順位
最後に、どこから手をつけるかの判断基準をお伝えします。
まず確認すべきは「今のユーザーがどのステージにいるか」です。新規ユーザーが多いなら AdMob 改善が先です。定着ユーザーが多いのに IAP やサブスクの転換率が低いなら、そこがボトルネックです。
統合ダッシュボードができたら、最初の 2 週間は「何も変えずにデータを見る」期間と決めることをおすすめします。思い込みで施策を打つより、実データから問題の所在を特定してから動いた方が、結果として速く収益が上がります。私自身、12 年間で何度も「打ち手の手数」と「観察の時間」のバランスを間違えてきました。最初の 2 週間を観察に充てると、その後の打ち手の精度が体感で 2 〜 3 倍違ってきます。
Antigravity はこのデータ収集・分析の部分を大幅に加速してくれます。今日からできる一歩は、まず Firebase Analytics と RevenueCat を接続して、収益データを Firestore に集約する部分だけ実装してみることです。そこから見えてくるデータが、次の施策を教えてくれます。
私自身まだ運用を磨いている途中ですが、同じ課題に取り組んでいる方の参考になれば嬉しいです。