π App Introduction: Personalized recipe generation and sharing platform powered by generative AI
π Duration: June 1, 2025 ~ July 4, 2025 (1 month)
π± Platform: Flutter cross-platform app (iOS, Android)
π₯ Team Size: 3 developers
πΌ Role: AI recipe generation, recipe editing, review features, reporting system, internationalization, etc.
π οΈ Key Technologies: Flutter
Dart
Firebase
Riverpod
MVVM
Gemini API
Firestore
Dio
Cloud Functions
Google Cloud Translation API
π GitHub: flutter-fantastic-four/cooki-app
π App Store: apps.apple.com/kr/app/cooki/id6747327839
Gemini AI
-based multimodal recipe generation system
Gemini 2.0-flash
model for multimodal recipe generation from text input and image recognitionFew-shot
techniques with user preferences (spiciness level, child-friendly options, etc.)Gemini 1.5-flash
modelFirestore
and Firebase Storage
integrationFlutter Image Compress
for image compression and resizing to reduce upload time and optimize storage costs, achieving a 35% reduction in API token usage and improved generation speedFirestore
subcollection structureGoogle Cloud Translation API
and Firebase Cloud Functions
Flutter l10n
SharedPreferences
and enabled real-time language switching via settingsFlutter Speech-to-Text
pluginShare Plus
packageCachedNetworkImage
Shimmer
loading animationsViewModel
try-catch blocksEnum
error codes, then mapped to internationalized messages in the UIViewModel
unit testing environment and proper separation of concernsMVVM
architecture with Repository
and DataSource
patterns for clear separation of concernsRiverpod
and feature-specific ViewModels
for predictable state updatesFirebase Crashlytics
1. Gemini AI
Model Selection and 2-Stage Validation System
Requirements
Need to generate high-quality recipes reliably from user text or image input while effectively blocking non-food-related inputs and malicious prompt manipulation attempts
Decision
Implemented 2-stage validation system separating Gemini 1.5-flash
and Gemini 2.0-flash
models by function
Gemini 1.5-flash
dedicated to input validation, filtering non-recipe inputs, command manipulation, and prompt injection attemptsGemini 2.0-flash
handles actual recipe generation, leveraging latest model performance and stability// Validation Model Configuration
_validationModel = googleAI.generativeModel(
model: 'gemini-1.5-flash',
generationConfig: GenerationConfig(
responseMimeType: 'application/json',
responseSchema: Schema.object(
properties: {'isValid': Schema.boolean()},
),
),
);
// Generation Model Configuration
_recipeGenerationModel = googleAI.generativeModel(
model: 'gemini-2.0-flash',
generationConfig: GenerationConfig(
responseMimeType: 'application/json',
responseSchema: Schema.object(/* Recipe structure definition */),
),
);
2. Firebase Cloud Functions-based Translation System
Requirements
Real-time translation functionality needed, but directly calling Google Translation API
from client poses security risk of API key exposure
Decision
Built serverless translation system using Firebase Cloud Functions
as intermediate layer
Google Cloud Translation API
credentials on server sideCloud Functions
level with structured responses to clientexports.translateText = onCall({ region: "asia-northeast3" }, async (request) => {
try {
const { text, targetLanguage, sourceLanguage } = request.data;
const translationRequest = {
parent: `projects/${projectId}/locations/global`,
contents: [text],
mimeType: 'text/plain',
targetLanguageCode: targetLanguage,
...(sourceLanguage && { sourceLanguageCode: sourceLanguage }),
};
const [response] = await translationClient.translateText(translationRequest);
return {
success: true,
translatedText: response.translations[0].translatedText,
detectedSourceLanguage: response.translations[0].detectedLanguageCode || sourceLanguage
};
} catch (error) {
throw new Error('Translation failed: ' + error.message);
}
});
3. Unified Logging and Crash Monitoring Utility
Requirements
Collaborative environment requires consistent error handling, and development team needs rapid identification and response to user environment exceptions after production deployment.
Decision
Developed logging utility with Firebase Crashlytics
integration
logError()
function for all exception handlinglog()
function, automatic collection in production via Crashlytics
runZonedGuarded
to detect and log Flutter framework-level exceptions, preventing app crashesvoid logError(
dynamic error,
StackTrace stack, {
String? reason,
bool fatal = false,
}) {
final message = reason != null
? '[EXCEPTION] $reason\n$error'
: '[EXCEPTION] $error';
log(message, stackTrace: stack);
FirebaseCrashlytics.instance.recordError(
error,
stack,
reason: reason,
fatal: fatal,
);
}
// Usage Example
try {
final bytes = await imageDownloadRepository.downloadImage(
recipe.imageUrl!,
);
...
} catch (e, stack) {
logError(e, stack, reason: 'Image download failed');
}
4. Multimodal Prompt Engineering
Requirements
Recipe generation must handle various scenarios including text input, image input, or combinations of both, while providing consistent quality results for both Korean and English users
Decision
Implemented template-based dynamic prompt system with multilingual markdown file structure
assets/prompts/ko/
, assets/prompts/en/
structure for language-specific prompt management__COOKI_*__
placeholders for dynamic runtime configurationFuture<String> _buildRecipePrompt({
String? textInput,
Set<String>? preferences,
required bool hasImage,
required String textOnlyRecipePromptPath,
required String imageRecipePromptPath,
}) async {
if (hasImage) {
String imagePrompt = await rootBundle.loadString(
'assets/prompts/$imageRecipePromptPath',
);
// Dynamic section configuration
String textContextSection = textInput?.isNotEmpty == true
? await _buildTextContextSection(textInput!)
: '';
String preferencesSection = await _buildPreferencesSection(preferences);
return imagePrompt
.replaceAll(AppConstants.textContextSectionPlaceholder, textContextSection)
.replaceAll(AppConstants.preferencesSectionPlaceholder, preferencesSection);
}
// Text-only prompt handling...
}
1. Recipe Generation and Image Upload Parallel Processing Optimization
Problem
Initial sequential processing approach required waiting for AI recipe generation completion before starting image upload, resulting in extended user wait times
Future.wait()
enables simultaneous execution of both tasks to reduce total processing time_saveRecipe()
method into separate _uploadImageIfNeeded()
methodFuture.wait()
// Original Sequential Processing
final generatedRecipe = await _generateRecipe(
imageBytes: compressedImageBytes, // Send Uint8List binary data to AI
textInput: state.textInput,
// ...
);
if (generatedRecipe != null) {
final imageUrl = await _uploadImageToStorage(compressedImageBytes, user.id);
final saved = await _saveRecipe(generatedRecipe, user, imageUrl);
}
// Improved Parallel Processing
final generationTask = _generateRecipe(...);
final imageUploadTask = _uploadImageIfNeeded(...);
final results = await Future.wait([generationTask, imageUploadTask]);
final generated = results[0] as GeneratedRecipe?;
final imageUrl = results[1] as String?;
2. Review Language Detection Optimization
Problem
Language detection API calls during review creation were processed synchronously, requiring users to wait over 3 seconds for review save completion, creating usability issues
await
keyword so language detection processes on a separate thread without UI blockingreviewId
for background language detection// Original Synchronous Processing
await saveReview(review);
await detectAndUpdateLanguage(reviewId); // UI blocking
// Improved Asynchronous Processing
final reviewId = await saveReview(review);
detectAndUpdateLanguage(reviewId); // Background execution without await
3. Image Optimization to Reduce API Costs and Improve Performance
Problem
High-resolution smartphone images sent directly to Gemini AI
increased tile count, raising API token usage and costs. Large file sizes caused slower uploads and delayed recipe generation
maxWidth: 768, maxHeight: 768
during picking to reduce tile countFlutter Image Compress
with 85% JPEG quality, reducing file size while maintaining acceptable image quality4. Filename Collision in Parallel Image Uploads
Firebase Storage
when uploading multiple images simultaneously, especially with 3+ imagesFirebase Storage
rules, quotas, and network connectivity β All normalDateTime.now().millisecondsSinceEpoch
created identical values during parallel processing, with collision potential in both compression and upload processesmillisecondsSinceEpoch
β microsecondsSinceEpoch
for 1000x higher precision