You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Firebase与Flutter实现食谱APP的收藏切换功能

Got it, let's walk through fixing and implementing your favorite toggle feature step by step, based on your existing code and requirements.

Step 1: Fix the Document ID Strategy

Your current updateFavourite method uses doc().set(), which generates a random document ID every time. This will create duplicate entries for the same recipe when a user toggles the favorite button multiple times. Instead, we'll use a unique ID for each user-recipe pair to ensure only one entry exists per user per recipe.

Add this helper function to generate the ID:

// Generate a unique document ID for each user-recipe combination
String getFavouriteDocId(String userId, String recipeId) {
  return '$userId-$recipeId';
}

Step 2: Implement the Toggle Favorite Core Method

Replace your existing updateFavourite and isFavourite methods with this single toggle function. It will check if the recipe is already in the user's favorites, then either remove it or add it accordingly.

Option 1: Delete Document When Unfavouriting (Most Efficient)

This approach removes the document entirely when the user unfavourites, keeping your collection clean:

Future<void> toggleFavouriteRecipe(FavouriteRecipesModel recipe) async {
  final docId = getFavouriteDocId(recipe.userId, recipe.recipeId);
  final docRef = favouriteDataCollection.doc(docId);
  
  final docSnapshot = await docRef.get();
  
  if (docSnapshot.exists) {
    // Remove from favorites
    await docRef.delete();
  } else {
    // Add to favorites
    await docRef.set({
      kFavouriteTitle: recipe.title,
      kFavouriteCookingTime: recipe.cookingTime,
      kFavouriteIngredients: recipe.ingredients,
      kFavouritePreparation: recipe.preparation,
      kFavouritePublishedDate: recipe.dateTime,
      kFavouriteNumOfLikes: recipe.numOfLikes,
      kFavouriteRecipeImage: recipe.imgPath,
      kFavouriteServings: recipe.servings,
      kIsFavourite: true, // Mark as favourite when adding
      kRecipeId: recipe.recipeId,
      kFavouriteRecipeCat: recipe.recipeBundleName,
      'uid': recipe.userId,
    });
  }
}

Option 2: Toggle isFavourite Field (If You Need to Keep the Document)

If you want to retain the document (e.g., for tracking historical preferences), update the isFavourite field instead of deleting:

Future<void> toggleFavouriteRecipe(FavouriteRecipesModel recipe) async {
  final docId = getFavouriteDocId(recipe.userId, recipe.recipeId);
  final docRef = favouriteDataCollection.doc(docId);
  
  final docSnapshot = await docRef.get();
  
  if (docSnapshot.exists) {
    // Toggle the favourite status
    final currentStatus = docSnapshot.data()[kIsFavourite] as bool;
    await docRef.update({kIsFavourite: !currentStatus});
  } else {
    // Add as favourite
    await docRef.set({
      // ... all fields as above
      kIsFavourite: true,
    });
  }
}

Step 3: Adjust the Favorite Recipes Stream

Update your stream to correctly fetch the user's favorites based on your chosen approach:

For Option 1 (Delete on Unfavourite)

Since we remove documents when unfavouriting, we only need to filter by the user's ID:

Stream<List<FavouriteRecipesModel>> get favouriteRecipes {
  return favouriteDataCollection
      .where('uid', isEqualTo: uid)
      .snapshots()
      .map(getFavouriteRecipes);
}

For Option 2 (Toggle isFavourite Field)

Keep the filter for isFavourite: true to only show active favorites:

Stream<List<FavouriteRecipesModel>> get favouriteRecipes {
  return favouriteDataCollection
      .where(kIsFavourite, isEqualTo: true)
      .where('uid', isEqualTo: uid)
      .snapshots()
      .map(getFavouriteRecipes);
}

Step 4: Implement the UI Toggle Button

In your recipe detail screen, add a button that checks the current favorite status and triggers the toggle method. Use a FutureBuilder to fetch the status asynchronously:

First, add a helper method to check if a recipe is favourited:

Future<bool> isRecipeFavourite(String userId, String recipeId) async {
  final docId = getFavouriteDocId(userId, recipeId);
  final docSnapshot = await favouriteDataCollection.doc(docId).get();
  
  // For Option 1: Check if the document exists
  return docSnapshot.exists;
  
  // For Option 2: Check if the document exists AND is marked as favourite
  // return docSnapshot.exists && docSnapshot.data()[kIsFavourite] as bool;
}

Then add the button to your UI:

// Inside your Recipe Detail Screen widget
FutureBuilder<bool>(
  future: isRecipeFavourite(currentUserId, recipe.recipeId), // Replace with your user ID and current recipe ID
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator(size: 24);
    }
    
    final isFavourited = snapshot.data ?? false;
    
    return IconButton(
      icon: Icon(
        isFavourited ? Icons.favorite : Icons.favorite_border,
        color: isFavourited ? Colors.redAccent : null,
        size: 32,
      ),
      onPressed: () {
        // Convert your RecipeModel to FavouriteRecipesModel
        final favouriteRecipe = FavouriteRecipesModel(
          title: recipe.title,
          cookingTime: recipe.cookingTime,
          ingredients: recipe.ingredients,
          preparation: recipe.preparation,
          dateTime: recipe.dateTime,
          numOfLikes: recipe.numOfLikes,
          imgPath: recipe.imgPath,
          servings: recipe.servings,
          recipeId: recipe.recipeId,
          recipeBundleName: recipe.recipeBundleName,
          userId: currentUserId,
          isFavourite: true, // Toggle will handle the actual status
          favouriteId: '', // Will be populated by the document ID
        );
        
        // Trigger the toggle
        toggleFavouriteRecipe(favouriteRecipe);
      },
    );
  },
)

Key Notes

  • Make sure currentUserId is the authenticated user's ID (from Firebase Auth).
  • Add error handling with try-catch blocks in the toggle method if you want to handle Firebase exceptions (e.g., network errors).
  • Your existing getFavouriteRecipes mapper function works perfectly with this setup, since it uses doc.id for favouriteId.

内容的提问来源于stack exchange,提问作者Mohammed Nadim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 12:27:31