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

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

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