Building a Recipe Card Widget with Favorite Toggle & Share Button in Flutter

06 Mar 2026

9K

35K

Building a Recipe Card Widget with Favorite Toggle & Share Button in Flutter

Creating engaging and interactive user interfaces is crucial for modern mobile applications. In Flutter, building reusable widgets that encapsulate both UI and functionality is a common practice. This article will guide you through the process of developing a "Recipe Card" widget, complete with a favorite toggle and a share button, enhancing user experience and interaction.

1. Defining the Recipe Model

First, let's establish a data model for our recipes. This model will hold all the necessary information for a single recipe, including its unique ID, title, image, ingredients, instructions, and a boolean to track its favorite status.


// lib/models/recipe_model.dart
class Recipe {
  final String id;
  final String title;
  final String imageUrl;
  final String ingredients;
  final String instructions;
  bool isFavorite; // This property will be mutable for the toggle

  Recipe({
    required this.id,
    required this.title,
    required this.imageUrl,
    required this.ingredients,
    required this.instructions,
    this.isFavorite = false, // Default to not favorite
  });
}

2. Adding Dependencies

To implement the share functionality, we'll use the popular share_plus package. Add it to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  share_plus: ^latest_version_available # e.g., ^7.0.2

After adding, run flutter pub get to fetch the package.

3. Building the Core Recipe Card Widget

Our recipe card will be a StatefulWidget because its internal state (the favorite status) can change. It will display the recipe's image, title, and other details, along with interactive buttons.


// lib/widgets/recipe_card_widget.dart
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart'; // Import for share functionality
import 'package:your_app_name/models/recipe_model.dart'; // Adjust import path

class RecipeCard extends StatefulWidget {
  final Recipe recipe;
  // Callback to inform the parent widget about favorite status changes
  final Function(String recipeId, bool isFavorite) onFavoriteToggle;

  const RecipeCard({
    Key? key,
    required this.recipe,
    required this.onFavoriteToggle,
  }) : super(key: key);

  @override
  State createState() => _RecipeCardState();
}

class _RecipeCardState extends State {
  late bool _isFavorite; // Internal state for favorite status

  @override
  void initState() {
    super.initState();
    _isFavorite = widget.recipe.isFavorite; // Initialize with recipe's current status
  }

  // Method to handle sharing the recipe details
  void _shareRecipe(Recipe recipe) {
    final String shareText =
        "Check out this delicious recipe: ${recipe.title}\n\n"
        "Ingredients: ${recipe.ingredients}\n\n"
        "Instructions: ${recipe.instructions}\n\n"
        "Find more recipes on #YourRecipeApp!"; // Customize with your app's link or hashtag

    Share.share(shareText, subject: 'Recipe: ${recipe.title}');
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(10),
      elevation: 5,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Image Section with overlayed title
          Stack(
            children: [
              ClipRRect(
                borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
                child: Image.network(
                  widget.recipe.imageUrl,
                  height: 200,
                  width: double.infinity,
                  fit: BoxFit.cover,
                  errorBuilder: (context, error, stackTrace) => Container(
                    height: 200,
                    color: Colors.grey[300],
                    child: const Center(child: Icon(Icons.broken_image, size: 50)),
                  ),
                ),
              ),
              Positioned(
                bottom: 10,
                right: 10,
                child: Container(
                  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                  decoration: BoxDecoration(
                    color: Colors.black54,
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Text(
                    widget.recipe.title,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                    softWrap: true,
                    overflow: TextOverflow.fade,
                  ),
                ),
              ),
            ],
          ),
          Padding(
            padding: const EdgeInsets.all(15),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Favorite and Share Buttons
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    // This Expanded widget is optional, if title is also here uncomment it
                    // Expanded(
                    //   child: Text(
                    //     widget.recipe.title,
                    //     style: Theme.of(context).textTheme.headlineSmall,
                    //     overflow: TextOverflow.ellipsis,
                    //   ),
                    // ),
                    Row(
                      children: [
                        IconButton(
                          icon: Icon(
                            _isFavorite ? Icons.favorite : Icons.favorite_border,
                            color: _isFavorite ? Colors.red : Colors.grey,
                          ),
                          onPressed: () {
                            setState(() {
                              _isFavorite = !_isFavorite; // Toggle internal state
                            });
                            // Call the callback to notify parent widget
                            widget.onFavoriteToggle(widget.recipe.id, _isFavorite);
                          },
                        ),
                        IconButton(
                          icon: const Icon(Icons.share, color: Colors.blueGrey),
                          onPressed: () {
                            _shareRecipe(widget.recipe); // Call share method
                          },
                        ),
                      ],
                    ),
                  ],
                ),
                const SizedBox(height: 10),
                Text(
                  'Ingredients:',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 5),
                Text(widget.recipe.ingredients),
                const SizedBox(height: 10),
                Text(
                  'Instructions:',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 5),
                Text(widget.recipe.instructions),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

4. Using the Recipe Card Widget

To display a list of recipe cards, you can use a ListView.builder within a StatefulWidget. The parent widget will manage the list of Recipe objects and provide the onFavoriteToggle callback.


// lib/screens/recipe_list_screen.dart (or your main.dart)
import 'package:flutter/material.dart';
import 'package:your_app_name/models/recipe_model.dart';
import 'package:your_app_name/widgets/recipe_card_widget.dart';

class RecipeListScreen extends StatefulWidget {
  const RecipeListScreen({Key? key}) : super(key: key);

  @override
  State createState() => _RecipeListScreenState();
}

class _RecipeListScreenState extends State {
  // Sample list of recipes
  final List _recipes = [
    Recipe(
      id: 'r1',
      title: 'Spaghetti Carbonara',
      imageUrl: 'https://images.unsplash.com/photo-1621996346534-e7703083e20e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80',
      ingredients: 'Spaghetti, Eggs, Pecorino Romano, Guanciale, Black Pepper',
      instructions: 'Cook spaghetti. Fry guanciale until crispy. Whisk eggs and cheese. Combine all ingredients in a warm pan with pasta water to create a creamy sauce.',
      isFavorite: true,
    ),
    Recipe(
      id: 'r2',
      title: 'Caprese Salad',
      imageUrl: 'https://images.unsplash.com/photo-1620584288015-32e6e3c0b0b8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80',
      ingredients: 'Tomatoes, Mozzarella, Fresh Basil, Olive Oil, Balsamic Glaze',
      instructions: 'Slice tomatoes and mozzarella. Arrange with basil leaves. Drizzle with olive oil and balsamic glaze. Season with salt and pepper to taste.',
    ),
    Recipe(
      id: 'r3',
      title: 'Chicken Tikka Masala',
      imageUrl: 'https://images.unsplash.com/photo-1589302631526-9d3e8e163b0b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80',
      ingredients: 'Chicken, Yogurt, Ginger-Garlic paste, Spices, Tomatoes, Cream',
      instructions: 'Marinate chicken in yogurt and spices. Grill or pan-fry chicken. Cook onions, ginger-garlic, and spices. Add tomatoes, cook, then add cream and chicken. Simmer.',
    ),
  ];

  // Callback function to handle favorite toggling from the RecipeCard
  void _toggleFavorite(String recipeId, bool isFavorite) {
    setState(() {
      final recipeIndex = _recipes.indexWhere((recipe) => recipe.id == recipeId);
      if (recipeIndex != -1) {
        _recipes[recipeIndex].isFavorite = isFavorite;
        // In a real application, you would also persist this change
        // to a local database (e.g., Hive, SQLite) or a backend server.
        print('Recipe ${recipeId} favorite status updated to: ${isFavorite}');
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('My Recipe Book'),
      ),
      body: ListView.builder(
        itemCount: _recipes.length,
        itemBuilder: (context, index) {
          return RecipeCard(
            recipe: _recipes[index],
            onFavoriteToggle: _toggleFavorite, // Pass the callback
          );
        },
      ),
    );
  }
}

Conclusion

You have successfully built a dynamic Recipe Card widget in Flutter, featuring a favorite toggle and a share button. This widget enhances user interaction by allowing them to mark recipes as favorites and easily share them with others. Key takeaways include:

  • Utilizing StatefulWidget to manage internal state changes (like the favorite status).
  • Implementing callbacks (onFavoriteToggle) to communicate state changes back to parent widgets.
  • Integrating external packages like share_plus for platform-specific functionalities.
  • Designing a clean and reusable UI component with Flutter's widget tree.

From here, you can further enhance this widget by adding navigation to a detailed recipe screen, integrating local storage for persistent favorite status, or connecting to a backend API for a more robust recipe management system.

Related Articles

Aug 02, 2026

Building a Flutter Notification Center with Grouping & Actions

Learn to create a sophisticated in-app notification center in Flutter, featuring grouping, swipe-to-dismiss, custom icons, and actionable buttons for enhanced u

Aug 01, 2026

Flutter Layout Tips: Using FractionallySizedBox, AspectRatio, ConstrainedBox for Adaptive UI

Master Flutter's FractionallySizedBox, AspectRatio, and ConstrainedBox to build truly adaptive UIs. Learn how to control widget sizing, maintain proportions, an

Aug 01, 2026

Flutter Product Quick View: Animation, Related Items, Badges

Learn to create a dynamic product quick view widget in Flutter, incorporating smooth animations, relevant related items, and eye-catching promo badges for an en