image

26 Mar 2026

9K

35K

Building a Shopping Cart Widget in Flutter: Featuring Discount Summary, Total Price, and Checkout Functionality

A well-designed shopping cart is a cornerstone of any e-commerce application. It provides users with a clear overview of their selected items, allows them to adjust quantities, apply discounts, and proceed to checkout. This article will guide you through building a robust Flutter shopping cart widget that includes a discount summary, calculates the total price, and features a checkout button.

1. Project Setup and Dependencies

First, create a new Flutter project. We'll use the provider package for state management to manage our cart's state efficiently.

Add the following dependency to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5

Then, run flutter pub get.

2. Data Models

We need simple models for our products and the items within our shopping cart.

product_model.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,
  });
}

cart_item_model.dart


import 'package:flutter_app/models/product_model.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;
}

3. Cart State Management (CartProvider)

We'll create a CartProvider using ChangeNotifier from the provider package. This class will hold the state of our shopping cart, including the list of items, discount information, and methods to manipulate them.

cart_provider.dart


import 'package:flutter/material.dart';
import 'package:flutter_app/models/cart_item_model.dart';
import 'package:flutter_app/models/product_model.dart';

class CartProvider with ChangeNotifier {
  final Map<String, CartItem> _items = {};
  double _discountPercentage = 0.0; // e.g., 0.10 for 10%
  String _promoCode = '';

  Map<String, CartItem> get items => {..._items};

  int get itemCount {
    return _items.length;
  }

  // Add an item to the cart
  void addItem(Product product) {
    if (_items.containsKey(product.id)) {
      _items.update(
        product.id,
        (existingCartItem) => CartItem(
          product: existingCartItem.product,
          quantity: existingCartItem.quantity + 1,
        ),
      );
    } else {
      _items.putIfAbsent(
        product.id,
        () => CartItem(
          product: product,
          quantity: 1,
        ),
      );
    }
    notifyListeners();
  }

  // Remove an item entirely from the cart
  void removeItem(String productId) {
    _items.remove(productId);
    notifyListeners();
  }

  // Decrease quantity of an item, remove if quantity becomes 0
  void removeSingleItem(String productId) {
    if (!_items.containsKey(productId)) {
      return;
    }
    if (_items[productId]!.quantity > 1) {
      _items.update(
        productId,
        (existingCartItem) => CartItem(
          product: existingCartItem.product,
          quantity: existingCartItem.quantity - 1,
        ),
      );
    } else {
      _items.remove(productId);
    }
    notifyListeners();
  }

  // Apply a discount code
  void applyDiscount(String code) {
    _promoCode = code;
    // Simple logic: If code is "SAVE10", apply 10% discount
    if (code.toLowerCase() == "save10") {
      _discountPercentage = 0.10; // 10%
    } else if (code.toLowerCase() == "save20") {
      _discountPercentage = 0.20; // 20%
    } else {
      _discountPercentage = 0.0; // No discount
    }
    notifyListeners();
  }

  // Get current promo code
  String get promoCode => _promoCode;

  // Calculate subtotal before discount
  double get subtotalAmount {
    double total = 0.0;
    _items.forEach((key, cartItem) {
      total += cartItem.totalPrice;
    });
    return total;
  }

  // Calculate discount amount
  double get discountAmount {
    return subtotalAmount * _discountPercentage;
  }

  // Calculate total amount after discount
  double get totalAmount {
    return subtotalAmount - discountAmount;
  }

  void clearCart() {
    _items.clear();
    _discountPercentage = 0.0;
    _promoCode = '';
    notifyListeners();
  }
}

4. Integrating Provider in main.dart

Wrap your root widget (e.g., MaterialApp) with a ChangeNotifierProvider so that the CartProvider instance is available throughout your application.

main.dart


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_app/providers/cart_provider.dart';
import 'package:flutter_app/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(),
      ),
    );
  }
}

5. Building the Shopping Cart UI

Now, let's create the UI for our shopping cart. This screen will list the items, provide quantity controls, allow discount application, show a summary, and include a checkout button.

shopping_cart_screen.dart


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_app/models/product_model.dart';
import 'package:flutter_app/providers/cart_provider.dart';

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

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

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

  // Dummy products to add to cart for demonstration
  final List<Product> _dummyProducts = [
    Product(id: 'p1', name: 'Laptop', price: 1200.00, imageUrl: 'https://via.placeholder.com/150'),
    Product(id: 'p2', name: 'Mouse', price: 25.00, imageUrl: 'https://via.placeholder.com/150'),
    Product(id: 'p3', name: 'Keyboard', price: 75.00, imageUrl: 'https://via.placeholder.com/150'),
    Product(id: 'p4', name: 'Monitor', price: 300.00, imageUrl: 'https://via.placeholder.com/150'),
  ];

  @override
  void dispose() {
    _promoCodeController.dispose();
    super.dispose();
  }

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

    return Scaffold(
      appBar: AppBar(
        title: const Text('Your Cart'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add_shopping_cart),
            onPressed: () {
              // Add some dummy products to cart for testing
              for (var product in _dummyProducts) {
                cart.addItem(product);
              }
            },
          ),
          IconButton(
            icon: const Icon(Icons.delete_forever),
            onPressed: () {
              cart.clearCart();
            },
          ),
        ],
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: cartItems.isEmpty
                ? const Center(child: Text('Your cart is empty!'))
                : ListView.builder(
                    itemCount: cartItems.length,
                    itemBuilder: (ctx, i) => CartItemWidget(
                      cartItem: cartItems[i],
                    ),
                  ),
          ),
          _buildDiscountSection(context, cart),
          _buildSummarySection(context, cart),
          _buildCheckoutButton(context, cart),
        ],
      ),
    );
  }

  Widget _buildDiscountSection(BuildContext context, CartProvider cart) {
    return Card(
      margin: const EdgeInsets.all(15),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'Apply Discount Code',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _promoCodeController,
                    decoration: InputDecoration(
                      hintText: 'e.g., SAVE10 or SAVE20',
                      suffixIcon: IconButton(
                        icon: const Icon(Icons.clear),
                        onPressed: () {
                          _promoCodeController.clear();
                          cart.applyDiscount(''); // Clear discount
                        },
                      ),
                    ),
                    onChanged: (value) {
                      // Optionally apply discount on change, or wait for button press
                    },
                  ),
                ),
                TextButton(
                  onPressed: () {
                    cart.applyDiscount(_promoCodeController.text);
                    FocusScope.of(context).unfocus(); // Dismiss keyboard
                  },
                  child: const Text('APPLY'),
                ),
              ],
            ),
            if (cart.promoCode.isNotEmpty && cart.discountAmount > 0)
              Padding(
                padding: const EdgeInsets.only(top: 8.0),
                child: Text(
                  'Applied Promo: ${cart.promoCode} (-${(cart.discountAmount / cart.subtotalAmount * 100).toStringAsFixed(0)}%)',
                  style: const TextStyle(color: Colors.green),
                ),
              ),
            if (cart.promoCode.isNotEmpty && cart.discountAmount == 0)
              Padding(
                padding: const EdgeInsets.only(top: 8.0),
                child: Text(
                  'Invalid Promo Code: "${cart.promoCode}"',
                  style: const TextStyle(color: Colors.red),
                ),
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildSummarySection(BuildContext context, CartProvider cart) {
    return Card(
      margin: const EdgeInsets.all(15),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                const Text(
                  'Subtotal',
                  style: TextStyle(fontSize: 18),
                ),
                Text(
                  '\$${cart.subtotalAmount.toStringAsFixed(2)}',
                  style: const TextStyle(fontSize: 18),
                ),
              ],
            ),
            const Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                const Text(
                  'Discount',
                  style: TextStyle(fontSize: 18, color: Colors.red),
                ),
                Text(
                  '-\$${cart.discountAmount.toStringAsFixed(2)}',
                  style: const TextStyle(fontSize: 18, color: Colors.red),
                ),
              ],
            ),
            const Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                const Text(
                  'Total',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                Text(
                  '\$${cart.totalAmount.toStringAsFixed(2)}',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                    color: Theme.of(context).primaryColor,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildCheckoutButton(BuildContext context, CartProvider cart) {
    return Padding(
      padding: const EdgeInsets.all(15.0),
      child: ElevatedButton(
        onPressed: cart.totalAmount <= 0
            ? null // Disable button if cart is empty or total is 0
            : () {
                // Implement your checkout logic here
                showDialog(
                  context: context,
                  builder: (ctx) => AlertDialog(
                    title: const Text('Checkout Complete!'),
                    content: Text('Your total order: \$${cart.totalAmount.toStringAsFixed(2)}'),
                    actions: <Widget>[
                      TextButton(
                        child: const Text('Okay'),
                        onPressed: () {
                          Navigator.of(ctx).pop();
                          cart.clearCart(); // Clear cart after successful checkout
                        },
                      ),
                    ],
                  ),
                );
              },
        style: ElevatedButton.styleFrom(
          minimumSize: const Size.fromHeight(50), // Make button full width
          backgroundColor: Theme.of(context).primaryColor,
        ),
        child: const Text(
          'CHECKOUT',
          style: TextStyle(fontSize: 18, color: Colors.white),
        ),
      ),
    );
  }
}

// Widget for displaying individual cart items
class CartItemWidget extends StatelessWidget {
  final CartItem cartItem;

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

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<CartProvider>(context, listen: false);
    return Dismissible(
      key: ValueKey(cartItem.product.id),
      direction: DismissDirection.endToStart,
      background: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        margin: const EdgeInsets.symmetric(
          horizontal: 15,
          vertical: 4,
        ),
        child: const Icon(
          Icons.delete,
          color: Colors.white,
          size: 40,
        ),
      ),
      confirmDismiss: (direction) {
        return showDialog(
          context: context,
          builder: (ctx) => AlertDialog(
            title: const Text('Are you sure?'),
            content: const Text('Do you want to remove the item from the cart?'),
            actions: <Widget>[
              TextButton(
                child: const Text('No'),
                onPressed: () {
                  Navigator.of(ctx).pop(false);
                },
              ),
              TextButton(
                child: const Text('Yes'),
                onPressed: () {
                  Navigator.of(ctx).pop(true);
                },
              ),
            ],
          ),
        );
      },
      onDismissed: (direction) {
        cart.removeItem(cartItem.product.id);
      },
      child: Card(
        margin: const EdgeInsets.symmetric(
          horizontal: 15,
          vertical: 4,
        ),
        child: Padding(
          padding: const EdgeInsets.all(8),
          child: ListTile(
            leading: CircleAvatar(
              backgroundImage: NetworkImage(cartItem.product.imageUrl),
              radius: 30,
            ),
            title: Text(cartItem.product.name),
            subtitle: Text(
              'Total: \$${cartItem.totalPrice.toStringAsFixed(2)}',
            ),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                IconButton(
                  icon: const Icon(Icons.remove),
                  onPressed: () {
                    cart.removeSingleItem(cartItem.product.id);
                  },
                ),
                Text('${cartItem.quantity}x'),
                IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: () {
                    cart.addItem(cartItem.product);
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Conclusion

In this article, we've walked through the process of building a comprehensive shopping cart widget in Flutter. We leveraged the provider package for efficient state management, created robust data models for products and cart items, and implemented a user-friendly UI. Key features include dynamic quantity adjustments, discount code application with a summary, and a clear display of subtotal, discount, and total price, culminating in a functional checkout button. This foundation can be extended further with persistent storage, complex discount rules, and integration with actual payment gateways.

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