π 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 FunctionsFlutter l10nSharedPreferences and enabled real-time language switching via settingsFlutter Speech-to-Text pluginShare Plus packageCachedNetworkImageShimmer 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 Crashlytics1. 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 CrashlyticsrunZonedGuarded 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