image

24 Feb 2026

9K

35K

Building a Shopping Cart Widget with Discount Summary in Flutter

A well-designed shopping cart is a crucial component of any e-commerce application, providing users with a clear overview of their selected items and the total cost. Enhancing this experience by including a dynamic discount summary can significantly improve user satisfaction and conversion rates. This article will guide you through building a Flutter shopping cart widget that efficiently displays cart items and calculates a detailed discount summary.

1. Understanding the Core Components

To create a robust shopping cart with discount capabilities, we need to define several key components:

  • Product Data Model: Represents the items available for purchase.
  • Cart Item Data Model: Stores information about an item currently in the cart, including its quantity.
  • Discount Data Model: Defines different types of discounts (e.g., percentage-based, fixed amount).
  • Cart Management Logic: A central class to handle adding/removing items, applying discounts, and calculating totals.
  • Cart Item Widget: A UI component to display individual items within the cart.
  • Discount Summary Widget: A UI component to show the subtotal, applied discount, and final total.

2. Data Models: Products, Cart Items, and Discounts

First, let's define the data structures for our products, cart items, and discounts. These models will be immutable for better data consistency.


// models/product.dart
class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

  Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
  });
}

// models/cart_item.dart
class CartItem {
  final Product product;
  int quantity;

  CartItem({
    required this.product,
    this.quantity = 1,
  });

  // Helper method to get total price for this item
  double get totalPrice => product.price * quantity;
}

// models/discount.dart
enum DiscountType { percentage, fixed }

class Discount {
  final String code;
  final double value; // e.g., 0.10 for 10% or 50.0 for $50 fixed
  final DiscountType type;

  Discount({
    required this.code,
    required this.value,
    required this.type,
  });
}

3. Managing the Shopping Cart State

We'll use a ChangeNotifier to manage the cart's state. This approach is highly compatible with state management solutions like Provider.


// providers/cart_provider.dart
import 'package:flutter/foundation.dart';
import '../models/product.dart';
import '../models/cart_item.dart';
import '../models/discount.dart';

class CartProvider with ChangeNotifier {
  final Map<String, CartItem> _items = {};
  Discount? _appliedDiscount;

  Map<String, CartItem> get items => {..._items}; // Return a copy
  int get itemCount => _items.values.fold(0, (sum, item) => sum + item.quantity);
  Discount? get appliedDiscount => _appliedDiscount;

  double get subtotal {
    return _items.values.fold(0.0, (sum, item) => sum + item.totalPrice);
  }

  double get discountAmount {
    if (_appliedDiscount == null) return 0.0;

    double amount = 0.0;
    if (_appliedDiscount!.type == DiscountType.percentage) {
      amount = subtotal * _appliedDiscount!.value;
    } else { // DiscountType.fixed
      amount = _appliedDiscount!.value;
    }
    return amount > subtotal ? subtotal : amount; // Discount cannot exceed subtotal
  }

  double get totalAmount {
    return subtotal - discountAmount;
  }

  void addItem(Product product) {
    if (_items.containsKey(product.id)) {
      _items.update(product.id, (existingItem) {
        existingItem.quantity += 1;
        return existingItem;
      });
    } else {
      _items.putIfAbsent(product.id, () => CartItem(product: product));
    }
    notifyListeners();
  }

  void removeItem(String productId) {
    _items.remove(productId);
    notifyListeners();
  }

  void increaseQuantity(String productId) {
    if (_items.containsKey(productId)) {
      _items.update(productId, (existingItem) {
        existingItem.quantity += 1;
        return existingItem;
      });
      notifyListeners();
    }
  }

  void decreaseQuantity(String productId) {
    if (!_items.containsKey(productId)) return;

    if (_items[productId]!.quantity > 1) {
      _items.update(productId, (existingItem) {
        existingItem.quantity -= 1;
        return existingItem;
      });
    } else {
      _items.remove(productId); // Remove item if quantity becomes 0
    }
    notifyListeners();
  }

  void applyDiscount(Discount discount) {
    _appliedDiscount = discount;
    notifyListeners();
  }

  void removeDiscount() {
    _appliedDiscount = null;
    notifyListeners();
  }

  void clearCart() {
    _items.clear();
    _appliedDiscount = null;
    notifyListeners();
  }
}

4. Building the Cart Item Widget

This widget will display the details of a single item in the cart, including its image, name, price, quantity, and controls to adjust the quantity.


// widgets/cart_item_card.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; // Assuming Provider is used
import '../models/cart_item.dart';
import '../providers/cart_provider.dart';

class CartItemCard extends StatelessWidget {
  final CartItem cartItem;

  const CartItemCard({super.key, required this.cartItem});

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<CartProvider>(context, listen: false);

    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 4),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(cartItem.product.imageUrl),
            radius: 25,
          ),
          title: Text(cartItem.product.name),
          subtitle: Text('Price: \$${cartItem.product.price.toStringAsFixed(2)}'),
          trailing: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Qty: ${cartItem.quantity}'),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    icon: const Icon(Icons.remove_circle_outline),
                    onPressed: () {
                      cart.decreaseQuantity(cartItem.product.id);
                    },
                  ),
                  IconButton(
                    icon: const Icon(Icons.add_circle_outline),
                    onPressed: () {
                      cart.increaseQuantity(cartItem.product.id);
                    },
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

5. Implementing Discount Logic and Summary Widget

The CartProvider already contains the logic for calculating discounts. Now, let's create a widget to display the financial summary, including the subtotal, applied discount, and final total.


// widgets/discount_summary.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';

class DiscountSummary extends StatelessWidget {
  const DiscountSummary({super.key});

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<CartProvider>(context);

    return Card(
      margin: const EdgeInsets.all(15),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Order Summary',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const Divider(),
            _buildSummaryRow('Subtotal:', '\$${cart.subtotal.toStringAsFixed(2)}'),
            if (cart.appliedDiscount != null)
              _buildSummaryRow(
                'Discount (${cart.appliedDiscount!.type == DiscountType.percentage ? '${(cart.appliedDiscount!.value * 100).toInt()}%' : '\$${cart.appliedDiscount!.value.toStringAsFixed(2)}'} off):',
                '-\$${cart.discountAmount.toStringAsFixed(2)}',
                isDiscount: true,
              ),
            const Divider(),
            _buildSummaryRow(
              'Total:',
              '\$${cart.totalAmount.toStringAsFixed(2)}',
              isBold: true,
              fontSize: 20,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSummaryRow(String label, String value, {bool isBold = false, bool isDiscount = false, double fontSize = 16}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            label,
            style: TextStyle(
              fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
              fontSize: fontSize,
            ),
          ),
          Text(
            value,
            style: TextStyle(
              fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
              fontSize: fontSize,
              color: isDiscount ? Colors.green : Colors.black,
            ),
          ),
        ],
      ),
    );
  }
}

6. Assembling the Shopping Cart Screen

Finally, we combine all the pieces into a single screen that displays the list of cart items, allows applying a discount code, and shows the total summary.


// screens/shopping_cart_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
import '../widgets/cart_item_card.dart';
import '../widgets/discount_summary.dart';
import '../models/product.dart'; // For dummy products
import '../models/discount.dart'; // For dummy discounts

class ShoppingCartScreen extends StatefulWidget {
  const ShoppingCartScreen({super.key});

  @override
  State<ShoppingCartScreen> createState() => _ShoppingCartScreenState();
}

class _ShoppingCartScreenState extends State<ShoppingCartScreen> {
  final TextEditingController _discountCodeController = TextEditingController();

  // Dummy products (for demonstration)
  final List<Product> _dummyProducts = [
    Product(id: 'p1', name: 'Laptop', price: 1200.00, imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Laptop'),
    Product(id: 'p2', name: 'Mouse', price: 25.00, imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Mouse'),
    Product(id: 'p3', name: 'Keyboard', price: 75.00, imageUrl: 'https://via.placeholder.com/150/00FF00/FFFFFF?text=Keyboard'),
  ];

  // Dummy discounts
  final Map<String, Discount> _availableDiscounts = {
    'SAVE10': Discount(code: 'SAVE10', value: 0.10, type: DiscountType.percentage), // 10% off
    'FLAT20': Discount(code: 'FLAT20', value: 20.00, type: DiscountType.fixed),    // $20 off
  };

  @override
  void initState() {
    super.initState();
    // Add some initial items to the cart for testing
    final cart = Provider.of<CartProvider>(context, listen: false);
    if (cart.itemCount == 0) {
      cart.addItem(_dummyProducts[0]); // Add Laptop
      cart.addItem(_dummyProducts[1]); // Add Mouse
      cart.addItem(_dummyProducts[1]); // Add another Mouse
    }
  }

  void _applyDiscount(BuildContext context, String code) {
    final cart = Provider.of<CartProvider>(context, listen: false);
    final discount = _availableDiscounts[code.toUpperCase()];

    if (discount != null) {
      cart.applyDiscount(discount);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Discount "${discount.code}" applied!')),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Invalid discount code.')),
      );
      cart.removeDiscount(); // Clear any previous invalid discount attempt
    }
  }

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<CartProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Your Shopping Cart'),
        actions: [
          IconButton(
            icon: const Icon(Icons.clear_all),
            onPressed: () {
              cart.clearCart();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Cart cleared!')),
              );
            },
          ),
        ],
      ),
      body: cart.itemCount == 0
          ? Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Your cart is empty!'),
                  ElevatedButton(
                    onPressed: () {
                      // Add some items for demonstration
                      cart.addItem(_dummyProducts[0]);
                      cart.addItem(_dummyProducts[1]);
                      cart.increaseQuantity(_dummyProducts[1].id);
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(content: Text('Some items added to cart.')),
                      );
                    },
                    child: const Text('Add some items'),
                  ),
                ],
              ),
            )
          : Column(
              children: [
                Expanded(
                  child: ListView.builder(
                    itemCount: cart.items.length,
                    itemBuilder: (ctx, i) {
                      return CartItemCard(
                        cartItem: cart.items.values.toList()[i],
                      );
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(15.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: TextField(
                          controller: _discountCodeController,
                          decoration: InputDecoration(
                            labelText: 'Discount Code',
                            border: const OutlineInputBorder(),
                            suffixIcon: cart.appliedDiscount != null
                                ? IconButton(
                                    icon: const Icon(Icons.cancel),
                                    onPressed: () {
                                      cart.removeDiscount();
                                      _discountCodeController.clear();
                                      ScaffoldMessenger.of(context).showSnackBar(
                                        const SnackBar(content: Text('Discount removed.')),
                                      );
                                    },
                                  )
                                : null,
                          ),
                        ),
                      ),
                      const SizedBox(width: 10),
                      ElevatedButton(
                        onPressed: () => _applyDiscount(context, _discountCodeController.text),
                        child: const Text('Apply'),
                      ),
                    ],
                  ),
                ),
                const DiscountSummary(),
                Padding(
                  padding: const EdgeInsets.all(15.0),
                  child: SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: () {
                        // Implement checkout logic here
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(content: Text('Proceeding to Checkout!')),
                        );
                      },
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 15),
                      ),
                      child: const Text('Checkout', style: TextStyle(fontSize: 18)),
                    ),
                  ),
                ),
              ],
            ),
    );
  }
}

To integrate this into your Flutter app, you would typically wrap your MaterialApp or a higher-level widget with a ChangeNotifierProvider from the provider package:


// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/cart_provider.dart';
import 'screens/shopping_cart_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CartProvider(),
      child: MaterialApp(
        title: 'Flutter Shopping Cart',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const ShoppingCartScreen(),
      ),
    );
  }
}

Conclusion

By following these steps, you can build a functional and user-friendly shopping cart widget in Flutter that includes a clear and dynamic discount summary. This structured approach, leveraging data models and state management (like ChangeNotifier with Provider), ensures maintainability and scalability. Further enhancements could include persistent cart storage, more complex discount rules, and integration with a backend API for product and discount validation.

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