目次
はじめに
月謝袋アプリに、プレミアムプランの案内ポップアップを実装しました。
ユーザーに自然な形でサブスクの価値を伝えるための工夫についてお話しします。
どんな機能?
レッスン日管理タブを開いたとき、画面下からスッとボトムシートが登場します。

設計のこだわり
「しつこくない」表示ルール
| 条件 | 動作 |
|---|---|
| プレミアム加入済み | 表示しない(永久) |
| 前回表示から90日未満 | 表示しない |
| 90日以上経過 | 再表示 |
shared_preferences でローカルに表示日時を保存しています。
static Future<bool> shouldShow(bool isPremium) async {
if (isPremium) return false;
final prefs = await SharedPreferences.getInstance();
final lastMs = prefs.getInt(_keyLastShown);
if (lastMs == null) return true;
final lastShown = DateTime.fromMillisecondsSinceEpoch(lastMs);
return DateTime.now().difference(lastShown).inDays >= 90;
}
先生にだけ表示
is_admin: true のユーザーのみに表示します。
生徒アカウントには一切表示されません。
final isAdmin = data?['is_admin'] as bool? ?? false;
final isPremium = data?['is_premium'] as bool? ?? false;
if (!isAdmin || isPremium) return;
比較表で価値を明確に

「何ができるようになるか」を一目で伝えるために
無料 vs プレミアムの比較表形式を採用しました。
テキストより表の方が、差が視覚的に伝わりやすいです。
実装のポイント
showModalBottomSheet でスッと登場
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => _PromoSheetContent(...),
);
backgroundColor: Colors.transparent にして、
コンテンツ側で BorderRadius.vertical(top: Radius.circular(20)) を指定すると
角丸のボトムシートになります。
タイミングは initState + addPostFrameCallback
タブが描画された直後に表示するため:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkAndShowPromo();
});
}
addPostFrameCallback を使うことで、
ウィジェットが完全に描画された後にダイアログを表示できます。
iOS・Android両対応確認済み
| プラットフォーム | 比較表表示 | 購入フロー遷移 | 閉じるボタン |
|---|---|---|---|
| iOS | ✅ | ✅ | ✅ |
| Android | ✅ | ✅ | ✅ |
おわりに
サブスクの案内は「しつこすぎず、でも確実に届ける」バランスが大切です。
3ヶ月ルールと比較表の組み合わせで、ユーザー体験を損なわない設計ができました。
次はいよいよApp Storeへの審査提出です 🚀
開発環境:Flutter / Firebase / RevenueCat / shared_preferences
GitHub:sinqwell-app/monthly-fee-stamp-app
