この記事は、Flutter + Firebase + AI で作るコーヒー焙煎ログアプリ開発シリーズの第7回です。
前回の記事: 【Flutter×Firebase】他ユーザープロフィール機能で拡がるコーヒーコミュニティを作った話
🎯 今回実装したもの
今回は大きく3つの改善を行いました:
- 目標ベース成功率システム - 味覚評価から目標達成度ベースへの変更
- アプリ全体の最適化 - 不要コード削除とサービス統合
- 統計機能の完全統合 - 高度なキャッシュシステムとパフォーマンス向上
📊 成功率計算の革新:味覚評価から目標達成へ
従来の問題点
これまでの成功率計算は味覚評価の平均値ベースでした:
// 従来の方式:味覚評価平均が3.0以上で成功
final avgTaste = (acidity + sweetness + bitterness) / 3;
if (avgTaste >= 3.0) successfulRoasts++;
この方式には以下の問題がありました:
- 主観的な評価に依存
- 目標が不明確
- スキル向上の指標として不適切
目標ベースシステムの実装
新しいシステムでは、焙煎前に目標フレーバープロファイルを設定し、実際の結果との誤差で成功を判定します:
// 新方式:目標との誤差±0.5以内で成功
final targetAcidity = (data['targetAcidity'] as num?)?.toDouble();
final targetSweetness = (data['targetSweetness'] as num?)?.toDouble();
final targetBitterness = (data['targetBitterness'] as num?)?.toDouble();
if (targetAcidity != null && targetSweetness != null && targetBitterness != null) {
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++;
}
目標設定UIの実装
焙煎セッション画面に目標設定スライダーを追加:
Widget _buildTargetFlavorSlider(String label, double value, ValueChanged<double> onChanged) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label: ${value.toStringAsFixed(1)}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
Slider(
value: value,
min: 1.0,
max: 5.0,
divisions: 8,
activeColor: AppTheme.fire,
onChanged: onChanged,
),
],
);
}
🚀 アプリ全体の最適化
不要ファイルの削除
以下の3つのファイルを削除してコードベースをクリーンアップ:
-
lib/services/unified_skill_service.dart
- 統一スキルサービス -
lib/screens/roasting_session_screen.dart
- 古い焙煎セッション画面 -
lib/services/user_statistics_service.dart
- 重複する統計サービス
サービスの統合
UserStatisticsService
を RoastStatisticsService
に統合し、重複するFirestoreクエリを削除:
// 統合前:2つの異なるサービス
final userStats = await UserStatisticsService.getUserStatistics(userId);
final roastStats = await RoastStatisticsService().getUserStatistics();
// 統合後:単一のサービス
final statistics = await RoastStatisticsService().getUserStatistics();
// コミュニティ投稿数も含む統合データを返す
⚡ 高度なキャッシュシステムの実装
2層キャッシュ戦略
ユーザータイプに応じて異なるキャッシュ期間を設定:
class _CacheEntry {
final RoastStatistics statistics;
final DateTime createdAt;
final bool isOtherUser;
bool isValid() {
final validDuration = isOtherUser
? Duration(minutes: 2) // 他ユーザー:短時間キャッシュ
: Duration(minutes: 5); // 自分:長時間キャッシュ
return DateTime.now().difference(createdAt).compareTo(validDuration) < 0;
}
}
LRU削除とサイズ制限
メモリ使用量を制御するため、最大20エントリの制限とLRU削除を実装:
static void _addToCache(String userId, RoastStatistics statistics, bool isOtherUser) {
// キャッシュサイズが上限に達している場合、最古のエントリを削除
if (_cache.length >= _maxCacheSize) {
_cleanupOldCache();
}
final key = isOtherUser ? 'other_$userId' : userId;
_cache[key] = _CacheEntry(
statistics: statistics,
createdAt: DateTime.now(),
isOtherUser: isOtherUser,
);
}
static void _cleanupOldCache() {
// 期限切れエントリを削除
final keysToRemove = _cache.entries
.where((entry) => !entry.value.isValid())
.map((entry) => entry.key)
.toList();
for (final key in keysToRemove) {
_cache.remove(key);
}
// まだ上限を超えている場合、最古のエントリを削除
if (_cache.length >= _maxCacheSize) {
final oldestKey = _cache.entries
.reduce((a, b) => a.value.createdAt.isBefore(b.value.createdAt) ? a : b)
.key;
_cache.remove(oldestKey);
}
}
🎮 チャレンジシステムの刷新
レベルベースから目標達成ベースへ全面的に変更しました。
初心者レベル(許容誤差±0.5)
static Map<String, dynamic> _createBeginnerTargetChallenge() {
return {
'title': '初めての目標設定チャレンジ',
'description': '酸味3.0、甘味3.5、苦味3.0を±0.5以内で達成してみましょう',
'targetAcidity': 3.0,
'targetSweetness': 3.5,
'targetBitterness': 3.0,
'tolerance': 0.5,
};
}
中級レベル(許容誤差±0.3)
static Map<String, dynamic> _createIntermediateHighPrecisionChallenge() {
return {
'title': '高精度目標達成',
'description': '±0.3以内の高精度で目標を達成してください',
'tolerance': 0.3,
};
}
上級レベル(許容誤差±0.2)
static Map<String, dynamic> _createAdvancedExtremePrecisionChallenge() {
return {
'title': 'エクストリーム精度チャレンジ',
'description': '±0.2以内の超高精度で目標を達成してください',
'tolerance': 0.2,
};
}
📈 パフォーマンス向上の成果
最適化結果
- キャッシュヒット率: 最大90%の応答時間短縮
- メモリ使用量: サイズ制限により安定化
- 削除されたコード: 約200行
- 解消されたエラー: コンパイルエラー3件、Lint警告全て
ユーザー体験の向上
- 成功率の意味向上: 自分の目標達成度が明確に
- リアルタイム統計: コミュニティ投稿数の正確な表示
- 高速レスポンス: キャッシュによる即座の統計表示
🔄 後方互換性の確保
古いデータとの互換性も維持:
if (targetAcidity != null && targetSweetness != null && targetBitterness != null) {
// 新方式:目標ベースの判定
final overallError = (acidityDiff + sweetnessDiff + bitternessDiff) / 3;
if (overallError <= 0.5) successfulRoasts++;
} else {
// 旧方式:味覚評価ベース(後方互換性)
final avgTaste = (acidity + sweetness + bitterness) / 3;
if (avgTaste >= 3.0) successfulRoasts++;
}
🛠️ 実装時の技術的課題と解決
1. メソッドスコープ問題
問題: _buildTargetFlavorSlider
が間違ったクラスで定義されていた
解決: 正しいクラス(_IntegratedRoastSessionScreenState
)に移動
2. キャッシュシステムの型不整合
問題: _CacheEntry
とRoastStatistics
の型管理
解決: 厳密な型管理で安全性を確保
3. コミュニティ投稿数の統合
問題: RoastStatistics
コンストラクタにcommunityPosts
パラメータが不足
解決: 必要なパラメータを追加し、統合メソッドを実装
🎉 まとめ
今回の実装により、以下の大きな改善を達成しました:
- 明確な目標設定: ユーザーが具体的な目標を持って焙煎に取り組める
- 客観的な成功指標: 主観的評価から客観的な達成度測定へ
- パフォーマンス大幅向上: キャッシュシステムによる高速化
- コードの品質向上: 重複削除と統合による保守性向上
次回は、目標達成の可視化機能やAIによる目標推奨機能の実装を予定しています。
シリーズ記事一覧
- 企画・設計編
- Firebase環境構築編
- 基本機能実装編
- AI統合編
- コミュニティ機能編
- 他ユーザープロフィール機能編
- 目標ベース成功率システム・最適化編 ← 今回
- 次回予告: 目標達成可視化機能編