image

28 Apr 2026

9K

35K

Building a Dynamic Shopping Cart Widget in Flutter with Discount Summary, Total Price, Checkout, and Promo Banner

A well-designed shopping cart is a cornerstone of any successful e-commerce application. It provides users with a clear overview of their selected items, enables easy quantity adjustments, displays applicable discounts, and guides them smoothly towards checkout. In this article, we will walk through the process of building a comprehensive shopping cart widget in Flutter, incorporating features like a discount summary, total price calculation, a checkout mechanism, and an engaging promo banner.

Core Components of Our Shopping Cart Widget

Our Flutter shopping cart will be composed of several key interactive and display elements:

  • Product Data Models: To represent items and their quantities.
  • Cart Management Logic: Using Provider for state management to handle adding, removing, and updating items.
  • Cart Item List: Displaying each product with its quantity and individual price.
  • Promo Banner: A visually appealing section to highlight ongoing offers.
  • Discount Summary: An input field for promo codes and a clear display of applied discounts.
  • Price Summary: Showing subtotal, discount amount, and the final total price.
  • Checkout Button: To initiate the checkout process.

Setting Up the Project and Data Models

First, ensure you have Flutter installed. Create a new Flutter project:


flutter create shopping_cart_app
cd shopping_cart_app

Add the `provider` package to your `pubspec.yaml` file:


dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5 # Use the latest version

Then run `flutter pub get`.

Next, let's define our basic data models: `Product` and `CartItem`.


// 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});

  double get totalPrice => product.price * quantity;
}

Cart Management with Provider

We'll use a `ChangeNotifier` to manage the state of our shopping cart, allowing our UI to react to changes. This `CartProvider` will hold the list of items, handle additions, removals, quantity updates, and apply promo codes.


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

class CartProvider extends ChangeNotifier {
  Map _items = {};
  String? _appliedPromoCode;
  double _discountPercentage = 0.0; // e.g., 0.10 for 10% off

  Map get items => {..._items}; // return a copy
  int get itemCount => _items.length;

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

  double get discountAmount {
    return subtotal * _discountPercentage;
  }

  double get total {
    return subtotal - discountAmount;
  }

  String? get appliedPromoCode => _appliedPromoCode;
  double get discountPercentage => _discountPercentage;

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

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

  void updateItemQuantity(String productId, int newQuantity) {
    if (newQuantity <= 0) {
      removeItem(productId);
    } else {
      _items.update(productId, (existingItem) =>
          CartItem(product: existingItem.product, quantity: newQuantity));
    }
    notifyListeners();
  }

  // --- Promo Code Logic ---
  void applyPromoCode(String promoCode) {
    // A simple mock for promo code logic
    if (promoCode.trim().toLowerCase() == 'save10') {
      _discountPercentage = 0.10; // 10% off
      _appliedPromoCode = promoCode;
    } else if (promoCode.trim().toLowerCase() == 'free15') {
      _discountPercentage = 0.15; // 15% off
      _appliedPromoCode = promoCode;
    } else {
      _discountPercentage = 0.0;
      _appliedPromoCode = null;
      // You might want to show an error message here
    }
    notifyListeners();
  }

  void removePromoCode() {
    _discountPercentage = 0.0;
    _appliedPromoCode = null;
    notifyListeners();
  }

  void clearCart() {
    _items = {};
    _discountPercentage = 0.0;
    _appliedPromoCode = null;
    notifyListeners();
  }
}

Building the Cart Item List Widget

Each item in the cart needs to display its details and allow quantity adjustments. We'll create a `CartItemWidget` for this.


// widgets/cart_item_widget.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/cart_item.dart';
import '../providers/cart_provider.dart';

class CartItemWidget extends StatelessWidget {
  final CartItem cartItem;

  CartItemWidget(this.cartItem);

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of(context, listen: false);
    return Card(
      margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
      child: Padding(
        padding: EdgeInsets.all(8),
        child: ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(cartItem.product.imageUrl),
          ),
          title: Text(cartItem.product.name),
          subtitle: Text('Price: \$${cartItem.product.price.toStringAsFixed(2)}'),
          trailing: Container(
            width: 120,
            child: Row(
              children: [
                IconButton(
                  icon: Icon(Icons.remove),
                  onPressed: () {
                    cart.updateItemQuantity(cartItem.product.id, cartItem.quantity - 1);
                  },
                ),
                Text('${cartItem.quantity}'),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    cart.updateItemQuantity(cartItem.product.id, cartItem.quantity + 1);
                  },
                ),
                IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () {
                    cart.removeItem(cartItem.product.id);
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Implementing Promo Banner and Discount Summary

We need a section for displaying promotional messages and another for users to input promo codes, along with a summary of the discount applied.


// widgets/promo_banner_widget.dart
import 'package:flutter/material.h';

class PromoBannerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(15),
      padding: EdgeInsets.all(15),
      decoration: BoxDecoration(
        color: Colors.lightBlue.shade100,
        borderRadius: BorderRadius.circular(10),
      ),
      child: Row(
        children: [
          Icon(Icons.local_offer, color: Colors.blue),
          SizedBox(width: 10),
          Expanded(
            child: Text(
              'Use code SAVE10 for 10% off or FREE15 for 15% off your order!',
              style: TextStyle(
                color: Colors.blue.shade800,
                fontSize: 16,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

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

class DiscountSummaryWidget extends StatefulWidget {
  @override
  _DiscountSummaryWidgetState createState() => _DiscountSummaryWidgetState();
}

class _DiscountSummaryWidgetState extends State {
  final TextEditingController _promoController = TextEditingController();

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

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

    if (cart.appliedPromoCode != null) {
      _promoController.text = cart.appliedPromoCode!;
    }

    return Card(
      margin: EdgeInsets.all(15),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Promo Code', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _promoController,
                    decoration: InputDecoration(
                      hintText: 'Enter promo code',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    if (_promoController.text.isNotEmpty) {
                      cart.applyPromoCode(_promoController.text);
                    }
                  },
                  child: Text('Apply'),
                ),
              ],
            ),
            if (cart.appliedPromoCode != null)
              Padding(
                padding: const EdgeInsets.only(top: 8.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      '${cart.appliedPromoCode} (${(cart.discountPercentage * 100).toStringAsFixed(0)}% off)',
                      style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold),
                    ),
                    InkWell(
                      onTap: () {
                        cart.removePromoCode();
                        _promoController.clear();
                      },
                      child: Text('Remove', style: TextStyle(color: Colors.red)),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Calculating and Displaying Total Price

This widget will summarize the subtotal, discount, and final total.


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

class PriceSummaryWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = Provider.of(context);

    return Card(
      margin: EdgeInsets.all(15),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            _buildPriceRow('Subtotal', cart.subtotal),
            _buildPriceRow('Discount', -cart.discountAmount, isDiscount: true),
            Divider(),
            _buildPriceRow('Total', cart.total, isTotal: true),
          ],
        ),
      ),
    );
  }

  Widget _buildPriceRow(String title, double amount, {bool isTotal = false, bool isDiscount = false}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            title,
            style: TextStyle(
              fontSize: isTotal ? 20 : 16,
              fontWeight: isTotal ? FontWeight.bold : FontWeight.normal,
              color: isDiscount ? Colors.red.shade700 : Colors.black,
            ),
          ),
          Text(
            '\$${amount.toStringAsFixed(2)}',
            style: TextStyle(
              fontSize: isTotal ? 20 : 16,
              fontWeight: isTotal ? FontWeight.bold : FontWeight.normal,
              color: isDiscount ? Colors.red.shade700 : Colors.black,
            ),
          ),
        ],
      ),
    );
  }
}

The Checkout Button

A simple button to trigger the checkout process. In a real application, this would navigate to a payment screen or confirm the order.


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

class CheckoutButtonWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = Provider.of(context);

    return Padding(
      padding: const EdgeInsets.all(15.0),
      child: SizedBox(
        width: double.infinity,
        child: ElevatedButton(
          onPressed: cart.itemCount > 0
              ? () {
                  // Implement your checkout logic here
                  // For example, navigate to a payment screen or confirm order.
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Proceeding to checkout with total \$${cart.total.toStringAsFixed(2)}')),
                  );
                  // Optionally clear the cart after checkout
                  // cart.clearCart();
                }
              : null, // Disable button if cart is empty
          style: ElevatedButton.styleFrom(
            padding: EdgeInsets.symmetric(vertical: 15),
            backgroundColor: Colors.blue, // Use primary or accent color
            foregroundColor: Colors.white,
            textStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
          ),
          child: Text('Checkout'),
        ),
      ),
    );
  }
}

Putting It All Together: The Main Cart Screen

Finally, let's assemble all these widgets into our main `ShoppingCartScreen`.


// screens/shopping_cart_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
import '../widgets/cart_item_widget.dart';
import '../widgets/promo_banner_widget.dart';
import '../widgets/discount_summary_widget.dart';
import '../widgets/price_summary_widget.dart';
import '../widgets/checkout_button_widget.dart';
import '../models/product.dart'; // Import Product model

class ShoppingCartScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = Provider.of(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('My Shopping Cart'),
      ),
      body: Column(
        children: [
          PromoBannerWidget(), // Display the promo banner
          Expanded(
            child: cart.itemCount == 0
                ? Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.shopping_cart_outlined, size: 80, color: Colors.grey),
                        SizedBox(height: 10),
                        Text('Your cart is empty!', style: TextStyle(fontSize: 20, color: Colors.grey)),
                        SizedBox(height: 20),
                        ElevatedButton(
                          onPressed: () {
                            // Example: Add some dummy products to cart for testing
                            cart.addItem(Product(id: 'p1', name: 'Laptop Pro', price: 1200.0, imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Laptop'));
                            cart.addItem(Product(id: 'p2', name: 'Wireless Mouse', price: 25.0, imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Mouse'));
                            cart.addItem(Product(id: 'p3', name: 'USB-C Hub', price: 50.0, imageUrl: 'https://via.placeholder.com/150/00FF00/FFFFFF?text=Hub'));
                          },
                          child: Text('Add Some Products'),
                        )
                      ],
                    ),
                  )
                : ListView.builder(
                    itemCount: cart.itemCount,
                    itemBuilder: (ctx, i) {
                      final cartItem = cart.items.values.toList()[i];
                      return CartItemWidget(cartItem);
                    },
                  ),
          ),
          if (cart.itemCount > 0)
            Column(
              children: [
                DiscountSummaryWidget(), // Promo code input and discount display
                PriceSummaryWidget(), // Subtotal, discount, and total
                CheckoutButtonWidget(), // Checkout button
              ],
            ),
        ],
      ),
    );
  }
}

Integrating with Your App

Finally, modify your `main.dart` to use the `CartProvider` and display the `ShoppingCartScreen`.


// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './providers/cart_provider.dart';
import './screens/shopping_cart_screen.dart';
import './models/product.dart'; // Import Product model if needed for initial items

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (ctx) => CartProvider(),
      child: MaterialApp(
        title: 'Flutter Shopping Cart',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: ShoppingCartScreen(),
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

Conclusion

You have now successfully built a comprehensive and dynamic shopping cart widget in Flutter. This solution incorporates essential e-commerce features such as state management with Provider, clear display of cart items, an interactive promo banner, a discount application mechanism, a transparent price summary, and a functional checkout button. This robust foundation can be further extended with features like persistent storage, different discount types, shipping calculations, and integration with payment gateways to create a full-fledged e-commerce experience.

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