image

04 Feb 2026

9K

35K

Building a Shopping Cart Summary Widget with Checkout Button in Flutter

Introduction

A shopping cart summary is a critical component in any e-commerce application. It provides users with a clear overview of the items they intend to purchase, including quantities, individual prices, and the calculated total. Integrating a prominent checkout button ensures a smooth transition from browsing to completing a transaction. This article will guide you through building a professional Flutter widget for a shopping cart summary, complete with a functional checkout button, using best practices for state management.

Prerequisites

Before diving in, ensure you have a basic understanding of Flutter development, including widgets, state management concepts (we'll be using the provider package for simplicity), and asynchronous operations.

1. Setting Up the Data Models and State Management

First, we need to define the data structures for our products and cart items. We'll also set up a state management solution to manage the cart's state across the application.

Product Model

A simple Product class to represent items available for purchase.


class Product {
  final String id;
  final String name;
  final double price;

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

CartItem Model

The CartItem class combines a Product with its chosen quantity.


class CartItem {
  final Product product;
  int quantity;

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

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

CartProvider (State Management with Provider)

We'll use a ChangeNotifier from the provider package to manage the cart's state. This class will hold the list of CartItems and provide methods to manipulate the cart.


import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; // Required for @required, although no longer strictly needed for ChangeNotifier

class CartProvider with ChangeNotifier {
  final List<CartItem> _items = [];

  List<CartItem> get items => [..._items]; // Return a copy to prevent external modification

  int get itemCount => _items.length;

  double get subtotal {
    double total = 0.0;
    for (var item in _items) {
      total += item.totalPrice;
    }
    return total;
  }

  double get taxRate => 0.08; // Example tax rate (8%)

  double get taxAmount => subtotal * taxRate;

  double get totalAmount => subtotal + taxAmount;

  void addItem(Product product) {
    int index = _items.indexWhere((item) => item.product.id == product.id);
    if (index >= 0) {
      _items[index].quantity++;
    } else {
      _items.add(CartItem(product: product));
    }
    notifyListeners();
  }

  void removeItem(String productId) {
    _items.removeWhere((item) => item.product.id == productId);
    notifyListeners();
  }

  void updateItemQuantity(String productId, int newQuantity) {
    int index = _items.indexWhere((item) => item.product.id == productId);
    if (index >= 0) {
      if (newQuantity > 0) {
        _items[index].quantity = newQuantity;
      } else {
        _items.removeAt(index); // Remove if quantity becomes 0 or less
      }
      notifyListeners();
    }
  }

  void clearCart() {
    _items.clear();
    notifyListeners();
  }
}

2. Designing the Cart Summary Widget

Now, let's build the UI for our cart summary. This widget will display each item, calculate totals, and house the checkout button.

CartItemTile Widget

A dedicated widget to display a single cart item. This helps keep the main summary widget clean.


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/models/cart_item.dart'; // Adjust import paths as needed
import 'package:your_app_name/providers/cart_provider.dart'; // Adjust import paths as needed

class CartItemTile extends StatelessWidget {
  final CartItem cartItem;

  const CartItemTile({Key? key, required this.cartItem}) : super(key: key);

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

    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 4),
      child: Padding(
        padding: const EdgeInsets.all(8),
        child: ListTile(
          leading: CircleAvatar(
            child: Padding(
              padding: const EdgeInsets.all(5),
              child: FittedBox(
                child: Text('\$${cartItem.product.price.toStringAsFixed(2)}'),
              ),
            ),
          ),
          title: Text(cartItem.product.name),
          subtitle: Text('Total: \$${cartItem.totalPrice.toStringAsFixed(2)}'),
          trailing: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconButton(
                icon: const Icon(Icons.remove_circle_outline),
                onPressed: () {
                  cartProvider.updateItemQuantity(cartItem.product.id, cartItem.quantity - 1);
                },
              ),
              Text('${cartItem.quantity}x'),
              IconButton(
                icon: const Icon(Icons.add_circle_outline),
                onPressed: () {
                  cartProvider.updateItemQuantity(cartItem.product.id, cartItem.quantity + 1);
                },
              ),
              IconButton(
                icon: const Icon(Icons.delete),
                color: Theme.of(context).errorColor,
                onPressed: () {
                  cartProvider.removeItem(cartItem.product.id);
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

The Main CartSummaryWidget

This widget will consume the CartProvider and display the list of items, summary totals, and the checkout button.


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/providers/cart_provider.dart'; // Adjust import paths as needed
import 'package:your_app_name/widgets/cart_item_tile.dart'; // Adjust import paths as needed
import 'package:your_app_name/screens/checkout_screen.dart'; // Adjust import paths as needed

class CartSummaryWidget extends StatelessWidget {
  const CartSummaryWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<CartProvider>(
      builder: (context, cartProvider, child) {
        return Column(
          children: [
            Expanded(
              child: cartProvider.items.isEmpty
                  ? Center(
                      child: Text(
                        'Your cart is empty!',
                        style: Theme.of(context).textTheme.headline6,
                      ),
                    )
                  : ListView.builder(
                      itemCount: cartProvider.itemCount,
                      itemBuilder: (ctx, i) => CartItemTile(
                        cartItem: cartProvider.items[i],
                      ),
                    ),
            ),
            if (cartProvider.items.isNotEmpty)
              Padding(
                padding: const EdgeInsets.all(15),
                child: Card(
                  margin: EdgeInsets.zero,
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      children: [
                        _buildSummaryRow(
                          context,
                          'Subtotal:',
                          '\$${cartProvider.subtotal.toStringAsFixed(2)}',
                        ),
                        const SizedBox(height: 8),
                        _buildSummaryRow(
                          context,
                          'Tax (${(cartProvider.taxRate * 100).toStringAsFixed(0)}%):',
                          '\$${cartProvider.taxAmount.toStringAsFixed(2)}',
                        ),
                        const Divider(),
                        _buildSummaryRow(
                          context,
                          'Total:',
                          '\$${cartProvider.totalAmount.toStringAsFixed(2)}',
                          isBold: true,
                          textSize: 20,
                        ),
                        const SizedBox(height: 20),
                        ElevatedButton(
                          onPressed: cartProvider.items.isEmpty
                              ? null
                              : () {
                                  // Perform checkout logic
                                  _performCheckout(context, cartProvider);
                                },
                          style: ElevatedButton.styleFrom(
                            minimumSize: const Size(double.infinity, 50), // Full width button
                          ),
                          child: const Text(
                            'Checkout',
                            style: TextStyle(fontSize: 18),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            SizedBox(height: cartProvider.items.isNotEmpty ? 10 : 0), // Small spacing if cart is not empty
          ],
        );
      },
    );
  }

  Widget _buildSummaryRow(BuildContext context, String title, String value,
      {bool isBold = false, double textSize = 16}) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          title,
          style: TextStyle(
            fontSize: textSize,
            fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
          ),
        ),
        Text(
          value,
          style: TextStyle(
            fontSize: textSize,
            fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
            color: isBold ? Theme.of(context).primaryColor : null,
          ),
        ),
      ],
    );
  }

  void _performCheckout(BuildContext context, CartProvider cartProvider) async {
    // Example: Simulate an asynchronous order placement
    // In a real app, this would involve API calls, payment processing, etc.
    final bool checkoutSuccess = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Confirm Checkout'),
        content: Text(
            'Are you sure you want to proceed with the payment of \$${cartProvider.totalAmount.toStringAsFixed(2)}?'),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(ctx).pop(false); // User cancelled
            },
            child: const Text('Cancel'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(ctx).pop(true); // User confirmed
            },
            child: const Text('Confirm'),
          ),
        ],
      ),
    ) ?? false; // Default to false if dialog is dismissed

    if (checkoutSuccess) {
      // Clear the cart after successful checkout
      cartProvider.clearCart();

      // Navigate to a checkout confirmation screen
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (ctx) => CheckoutScreen(
            orderAmount: cartProvider.totalAmount,
          ),
        ),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Checkout cancelled.')),
      );
    }
  }
}

3. Integrating the Checkout Button Logic

The _performCheckout method demonstrates how to handle the checkout action. It simulates a confirmation dialog and, upon success, clears the cart and navigates to a `CheckoutScreen`.

CheckoutScreen Placeholder

A simple screen to confirm the checkout was successful.


import 'package:flutter/material.dart';

class CheckoutScreen extends StatelessWidget {
  final double orderAmount;

  const CheckoutScreen({Key? key, required this.orderAmount}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Checkout Confirmation'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.check_circle_outline,
              color: Colors.green,
              size: 100,
            ),
            const SizedBox(height: 20),
            const Text(
              'Your order has been placed successfully!',
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 10),
            Text(
              'Total amount: \$${orderAmount.toStringAsFixed(2)}',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: () {
                // Navigate back to the home screen or product listing
                Navigator.of(context).popUntil((route) => route.isFirst);
              },
              child: const Text('Continue Shopping'),
            ),
          ],
        ),
      ),
    );
  }
}

4. Using the Widget in Your Application

To make the CartProvider and CartSummaryWidget work, you need to provide the CartProvider at a higher level in your widget tree, typically in main.dart or your main app widget.

main.dart Setup


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/providers/cart_provider.dart'; // Adjust import paths
import 'package:your_app_name/models/product.dart'; // Adjust import paths
import 'package:your_app_name/widgets/cart_summary_widget.dart'; // Adjust import paths

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

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

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

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

    // Dummy products for demonstration
    final List<Product> products = [
      Product(id: 'p1', name: 'Laptop', price: 1200.00),
      Product(id: 'p2', name: 'Mouse', price: 25.00),
      Product(id: 'p3', name: 'Keyboard', price: 75.00),
      Product(id: 'p4', name: 'Monitor', price: 300.00),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('My Shop'),
        actions: [
          IconButton(
            icon: const Icon(Icons.shopping_cart),
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (ctx) => Scaffold(
                    appBar: AppBar(title: const Text('Your Cart')),
                    body: const CartSummaryWidget(),
                  ),
                ),
              );
            },
          ),
        ],
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: products.length,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        itemBuilder: (ctx, i) => Card(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                products[i].name,
                style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              Text('\$${products[i].price.toStringAsFixed(2)}'),
              ElevatedButton(
                onPressed: () {
                  cartProvider.addItem(products[i]);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('${products[i].name} added to cart!'),
                      duration: const Duration(milliseconds: 700),
                    ),
                  );
                },
                child: const Text('Add to Cart'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Conclusion

You have successfully built a dynamic shopping cart summary widget in Flutter, complete with state management using the provider package and a functional checkout button. This setup provides a solid foundation for any e-commerce application, allowing users to review their selections and proceed to purchase seamlessly.

Further enhancements could include:

  • Adding product images and detailed descriptions.
  • Implementing a more sophisticated quantity selector with direct input.
  • Integrating with a backend API for real order processing and payment gateways.
  • Adding animations for cart updates.
  • Applying discount codes or shipping options.

By following this guide, you now have a robust and extensible shopping cart solution for your Flutter applications.

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