前回までのおさらい:コミュニティ機能の完成から
前回の記事では、Threads風SNS機能の実装でコーヒー焙煎師たちのコミュニティが誕生しました。しかし、開発はここで終わりません。
28.mdの開発計画書に記載された3つの短期改善項目が残されていました:
- 目標達成グラフの可視化機能
- 目標設定のプリセット機能
- チャレンジ進捗の詳細表示機能
これらの実装を通じて、今回はAI協働開発そのものの進化を体験することになります。
第一幕:目標達成グラフが描く成長の軌跡 📈
データ可視化の新次元
class TargetAchievementData {
int totalAttempts; // 総挑戦回数
int targetAchievements; // 目標達成回数
double averageError; // 平均誤差
final DateTime month; // 月
/// 達成率(0.0〜1.0)
double get achievementRate {
if (totalAttempts == 0) return 0.0;
return targetAchievements / totalAttempts;
}
/// パーセント表示
String get achievementRateFormatted {
return '${(achievementRate * 100).toStringAsFixed(1)}%';
}
}
これまでの焙煎アプリでは「今回の焙煎がどうだったか」しか分からませんでした。
しかし、月別の目標達成率を可視化することで、ユーザーの成長の軌跡が見えるようになったのです。
fl_chartライブラリとの出会い
LineChart(
LineChartData(
gridData: FlGridData(show: true),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text('${value.toInt()}月');
},
),
),
),
lineBarsData: [
LineChartBarData(
spots: achievementSpots,
isCurved: true,
gradient: LinearGradient(
colors: [AppTheme.success, AppTheme.aiAssistant],
),
barWidth: 3,
dotData: FlDotData(show: true),
),
],
),
)
fl_chartライブラリの威力は圧倒的でした。
わずか数行のコードで、プロフェッショナルレベルのグラフが実現できるのです。
成功判定ロジックの革命
従来の味覚評価ベースから、目標ベースの判定に変更:
// 従来:主観的な味覚評価
final avgTaste = (acidity + sweetness + bitterness) / 3;
if (avgTaste >= 3.0) successfulRoasts++;
// 新方式:客観的な目標達成判定(±0.5許容誤差)
final acidityDiff = (acidity - targetAcidity).abs();
final sweetnessDiff = (sweetness - targetSweetness).abs();
final bitternessDiff = (bitterness - targetBitterness).abs();
final overallError = (acidityDiff + sweetnessDiff + bitternessDiff) / 3;
if (overallError <= 0.5) successfulRoasts++;
これにより、より科学的で公平な成功判定が可能になりました。
第二幕:プリセット機能がもたらすUX革命 🎨
「理想の味」をワンクリックで
初心者焙煎師にとって最大の障壁は**「目標をどう設定すればいいか分からない」**ことでした。
そこで誕生したのが、4つの内蔵プリセット:
static const List<TargetPreset> builtInPresets = [
TargetPreset(
name: 'バランス型',
description: '酸味・甘味・苦味のバランスが取れたオールラウンダー',
acidity: 3.5,
sweetness: 3.5,
bitterness: 3.0,
),
TargetPreset(
name: '明るい酸味',
description: 'フルーティーで爽やかな酸味が特徴',
acidity: 4.5,
sweetness: 3.0,
bitterness: 2.5,
),
TargetPreset(
name: '甘み重視',
description: 'チョコレートのような深い甘みを重視',
acidity: 3.0,
sweetness: 4.5,
bitterness: 2.5,
),
TargetPreset(
name: 'ダークビター',
description: '力強い苦味とコクが楽しめる本格派',
acidity: 2.5,
sweetness: 3.0,
bitterness: 4.5,
),
];
Firestoreでのカスタムプリセット管理
class TargetPresetService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
/// カスタムプリセットを保存
Future<void> saveCustomPreset(TargetPreset preset) async {
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId == null) throw Exception('ユーザーが認証されていません');
await _firestore
.collection('users')
.doc(userId)
.collection('targetPresets')
.add(preset.toMap());
}
/// プリセット一覧を取得(内蔵 + カスタム)
Future<List<TargetPreset>> getAllPresets() async {
final customPresets = await getCustomPresets();
return [...builtInPresets, ...customPresets];
}
}
この設計により、スケーラブルな味覚プロファイル管理が実現しました。
UIの進化:フレーバーチップ
Widget _buildFlavorChips(TargetPreset preset) {
return Wrap(
spacing: 8,
children: [
Chip(
label: Text('酸味 ${preset.acidity}'),
backgroundColor: _getFlavorColor(preset.acidity),
),
Chip(
label: Text('甘味 ${preset.sweetness}'),
backgroundColor: _getFlavorColor(preset.sweetness),
),
Chip(
label: Text('苦味 ${preset.bitterness}'),
backgroundColor: _getFlavorColor(preset.bitterness),
),
],
);
}
視覚的に味覚プロファイルが理解できる、直感的なUIの完成です。
第三幕:レースコンディション解決 - 開発者魂の勝利 🔧
謎の現象に立ち向かう
冒頭で紹介した**「3個vs8個チャレンジ」問題**。
この解決過程こそが、今回最大の技術的ハイライトでした。
問題の根本原因
調査の結果、3つの複合的な問題が判明:
-
デュアルデータソース問題
- 画面側:8個のチャレンジ(全レベル混合)
- サービス側:3個のチャレンジ(スキルレベル別)
-
レースコンディション
// 危険な並行処理 WidgetsBinding.instance.addPostFrameCallback((_) { _loadDefaultChallengesImmediately(); // 8個表示 _loadChallenges(); // 3個の可能性 → 競合状態 });
-
アニメーションタイミング問題
Transform.scale( scale: _cardAnimation.value, // これが0.0で見えない状態 child: ChallengeCard(), )
解決策:統一データソース + シーケンシャルローディング
1. データソースの統一
// サービス層で統一管理
class RoastChallengeService {
/// 全レベル含む統一チャレンジ生成
List<RoastChallenge> getDefaultChallenges() {
return [
// 初心者向け(3個)
RoastChallenge.beginner1(),
RoastChallenge.beginner2(),
RoastChallenge.beginner3(),
// 中級者向け(3個)
RoastChallenge.intermediate1(),
RoastChallenge.intermediate2(),
RoastChallenge.intermediate3(),
// 上級者向け(2個)
RoastChallenge.advanced1(),
RoastChallenge.advanced2(),
]; // 合計8個で統一
}
}
2. 明確な状態管理
enum ChallengeDisplayMode {
loading, // ローディング中
defaultChallenges, // デフォルトチャレンジ表示
aiChallenges, // AI生成チャレンジ表示
error, // エラー状態
}
3. シーケンシャルローディング
Future<void> _loadChallengesSequentially() async {
debugPrint('🎯 シーケンシャルローディング開始');
try {
// 1. 即座にデフォルトチャレンジを表示
setState(() {
_displayMode = ChallengeDisplayMode.defaultChallenges;
_challenges = _challengeService.getDefaultChallenges();
_isLoading = false;
_lastError = null;
});
debugPrint('🎯 デフォルトチャレンジ表示完了: ${_challenges.length}個');
_cardAnimationController.forward();
// 2. バックグラウンドで統計データを取得
await _loadUserData();
// 3. AI生成は手動実行のみ(自動実行停止)
debugPrint('🎯 シーケンシャルローディング完了');
} catch (e) {
setState(() {
_displayMode = ChallengeDisplayMode.error;
_lastError = e.toString();
});
}
}
この解決により、レースコンディションが完全に解消され、予測可能で安定したチャレンジ表示が実現しました。
第四幕:AI協働開発の新次元 🤖
Claudeとの「ペアプロ」体験
今回の開発で最も印象的だったのは、Claude Codeとの協働レベルの向上でした。
自動的な問題分析
Claudeの分析:
「レースコンディションが発生しています。2つの非同期処理が
競合状態を起こし、データ不整合が生じています。」
「解決策として、シーケンシャルローディングパターンを
提案します。統一データソースと明確な状態管理も必要です。」
段階的なリファクタリング提案
Claude Codeは複雑な問題を小さなステップに分解して提示:
- ✅ デフォルトチャレンジ生成を一箇所に集約
- ✅ AI/デフォルトの状態管理を明確に分離
- ✅ シーケンシャルローディングでレースコンディション解消
- ✅ 重複コードの除去とエラーハンドリング統一
TODOの自動管理
// Claude Codeが自動で管理したTODO
[
{"content": "デフォルトチャレンジ生成を一箇所に集約", "status": "completed"},
{"content": "AI/デフォルトの状態管理を明確に分離", "status": "completed"},
{"content": "シーケンシャルローディングでレースコンディション解消", "status": "completed"},
{"content": "重複コードの除去とエラーハンドリング統一", "status": "completed"}
]
品質向上の実感
今回のリファクタリングにより、以下の劇的な改善を実現:
- バグ耐性: ★★★★★(レースコンディション解消)
- 保守性: ★★★★★(統一データソース)
- 予測可能性: ★★★★★(明確な状態管理)
- コード品質: ★★★★★(重複コード削除)
エピローグ:Git 7コミットの物語 📝
今回の開発成果は、段階的な7つのコミットとして記録されました:
04990dd docs: 設計書・仕様書を今日の成果で全面更新
504904e docs: プロジェクト設定と開発ドキュメントを更新
fb628af feat: プロフィール機能と関連画面の統合改善
942d608 chore: 不要なファイルとAPI監視機能を削除
c3ac815 refactor: チャレンジシステムの根本的リファクタリング
463f77f feat: 目標設定のプリセット機能を実装
09b84fc feat: 目標達成グラフの可視化機能を実装
各コミットには詳細な説明と実装の背景が記載され、将来の開発者(自分自身を含む)への完璧な道しるべとなっています。
学びと気づき:次世代開発への示唆 💡
AI協働開発の進化パターン
今回の体験で確信したのは、AI協働開発には明確な進化段階があることです:
Level 1: AI = ツール
- Stack Overflowの代替
- コード補完程度の活用
Level 2: AI = アシスタント
- バグ修正の支援
- 実装アドバイスの提供
Level 3: AI = パートナー(今回到達)
- 問題の根本原因分析
- 設計レベルでの改善提案
- 段階的なリファクタリング戦略
Level 4: AI = チームメンバー(未来)
- プロジェクト全体の戦略立案
- アーキテクチャ設計の主導
- 品質保証の完全自動化
技術的学習の加速
fl_chartライブラリの習得も、Claude Codeとの協働により劇的に短縮されました:
// 30分でマスターしたグラフ実装
LineChartBarData(
spots: monthlyData.entries.map((entry) {
final month = entry.key.month.toDouble();
final rate = entry.value.achievementRate;
return FlSpot(month, rate);
}).toList(),
isCurved: true,
gradient: LinearGradient(
colors: [AppTheme.success.withOpacity(0.8), AppTheme.aiAssistant],
),
)
従来なら1-2日かかる学習が、30分で完了したのです。
UX設計思考の深化
プリセット機能の実装を通じて、**「技術とUXの融合」**の重要性を学びました:
- 技術的実装: Firestoreでのデータ管理
- UX設計: 初心者でも直感的に使える操作性
- 視覚的表現: フレーバーチップによる味覚可視化
この3つが完璧に調和したとき、真に価値のある機能が生まれるのです。
未来への展望:次回予告 🚀
プロジェクト統計の進化
- コード行数: 15,000行(+2,000行)
- ファイル数: 42ファイル(+2ファイル)
- アーキテクチャ成熟度: Level 6 (Advanced Production)
- 完成度: 95%
次回実装予定
Phase 8では、以下の高度な機能に挑戦します:
- FCM通知システム: リアルタイム通知でユーザーエンゲージメント向上
- 検索機能強化: ユーザー・投稿・タグの高速検索
- データエクスポート: 焙煎データのCSV出力機能
- オフライン対応: ネットワーク不安定時の堅牢性確保
AI協働開発の未来
今回の体験で、AIとの協働開発が単なる効率化ツールを超えて、開発体験そのものを変革する力を持つことを確信しました。
近い将来、**「AIとのコミュニケーション能力」**がエンジニアの必須スキルになる日が来るでしょう。
そして、その先には**「AIチーム開発」**という新しい開発パラダイムが待っているのかもしれません。
おわりに:技術と感情の融合点で
コーヒー焙煎アプリの開発は、単なる技術的な挑戦を超えて、人間とAIの協創体験となりました。
目標達成グラフは単なるデータ可視化ではなく、ユーザーの成長の物語を描く機能です。
プリセット機能は単なる設定機能ではなく、初心者の学習の扉を開く機能です。
レースコンディション解決は単なるバグ修正ではなく、品質への執念の表れです。
コーヒーという、最も人間的で感情的な飲み物を題材にしながら、
最先端のAI技術と融合させることで、
新しい開発体験の可能性を探求し続けています。
次回の記事も、きっと想像を超える発見と学びが待っているはずです。
🔗 関連リンク
📝 技術スタック
- Flutter: 3.19.0
- Firebase: Auth + Firestore
- AI: Google Gemini 1.5 Flash
- Charts: fl_chart 0.68.0
- 開発支援: Claude Code
最後まで読んでいただき、ありがとうございました!
AI協働開発の世界は、想像以上に深く、魅力的です。
いいね・ストック・コメントで応援いただけると、次の開発がますます楽しくなります 🚀
続編にもご期待ください!
#Flutter #Firebase #AI #Claude #Gemini #データ可視化 #UX設計 #個人開発 #協働開発