image

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

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is