Building a Shopping List Widget with Swipe-to-Delete in Flutter

15 Jan 2026

9K

35K

Building a Shopping List Widget with Swipe-to-Delete in Flutter

Creating interactive and intuitive user interfaces is crucial for modern mobile applications. A common pattern in list-based apps is the ability to easily remove items. In Flutter, the Dismissible widget provides an elegant solution for implementing swipe-to-delete functionality, offering a satisfying user experience. This article will guide you through building a simple shopping list widget where users can add items and remove them with a swipe gesture.

Prerequisites

Before you begin, ensure you have:

  • Flutter SDK installed and configured.
  • A basic understanding of Flutter widgets, state management (StatefulWidget), and lists.

1. The Shopping List Item Data Model

First, let's define a simple data model for our shopping list items. Each item will have a name and a unique ID.


class ShoppingListItem {
  final String id;
  String name;
  bool isCompleted; // Optional: to mark items as bought

  ShoppingListItem({required this.id, required this.name, this.isCompleted = false});
}

2. Basic Application Structure

We'll start with a basic Flutter application structure, setting up our main entry point and a MaterialApp.


import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart'; // For generating unique IDs

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shopping List',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ShoppingListScreen(),
    );
  }
}

// Data model (add this before ShoppingListScreen or in a separate file)
class ShoppingListItem {
  final String id;
  String name;
  bool isCompleted;

  ShoppingListItem({required this.id, required this.name, this.isCompleted = false});
}

3. Creating the ShoppingListScreen

Our shopping list screen will be a StatefulWidget because we need to manage the list of items (add, remove) and update the UI accordingly.


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

  @override
  State createState() => _ShoppingListScreenState();
}

class _ShoppingListScreenState extends State {
  final List _shoppingItems = [
    ShoppingListItem(id: const Uuid().v4(), name: 'Milk'),
    ShoppingListItem(id: const Uuid().v4(), name: 'Bread'),
    ShoppingListItem(id: const Uuid().v4(), name: 'Eggs'),
  ];

  final TextEditingController _itemController = TextEditingController();
  final Uuid _uuid = const Uuid();

  // Method to add a new item
  void _addItem(String name) {
    if (name.isNotEmpty) {
      setState(() {
        _shoppingItems.add(ShoppingListItem(id: _uuid.v4(), name: name));
      });
      _itemController.clear();
    }
  }

  // Method to remove an item (used by Dismissible)
  void _removeItem(String id) {
    setState(() {
      _shoppingItems.removeWhere((item) => item.id == id);
    });
  }

  // Method to toggle completion status (optional)
  void _toggleCompletion(String id) {
    setState(() {
      final index = _shoppingItems.indexWhere((item) => item.id == id);
      if (index != -1) {
        _shoppingItems[index].isCompleted = !_shoppingItems[index].isCompleted;
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('My Shopping List'),
      ),
      body: ListView.builder(
        itemCount: _shoppingItems.length,
        itemBuilder: (context, index) {
          final item = _shoppingItems[index];
          // We'll replace this with Dismissible in the next step
          return ListTile(
            title: Text(item.name),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddItemDialog(context),
        child: const Icon(Icons.add),
      ),
    );
  }

  // Dialog to add new items
  Future _showAddItemDialog(BuildContext context) async {
    return showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Add New Item'),
          content: TextField(
            controller: _itemController,
            decoration: const InputDecoration(hintText: 'Enter item name'),
            autofocus: true,
            onSubmitted: (value) {
              _addItem(value);
              Navigator.of(context).pop();
            },
          ),
          actions: [
            TextButton(
              child: const Text('Cancel'),
              onPressed: () {
                _itemController.clear();
                Navigator.of(context).pop();
              },
            ),
            TextButton(
              child: const Text('Add'),
              onPressed: () {
                _addItem(_itemController.text);
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }
}

Note: You'll need to add the uuid package to your pubspec.yaml for unique IDs:


dependencies:
  flutter:
    sdk: flutter
  uuid: ^4.2.2 # Use the latest version

Then run flutter pub get.

4. Implementing Swipe-to-Delete with Dismissible

Now, let's integrate the Dismissible widget into our ListView.builder. The Dismissible widget requires a unique key, a background widget (what appears behind the item when swiping), and an onDismissed callback.


// ... inside _ShoppingListScreenState's build method ...

      body: ListView.builder(
        itemCount: _shoppingItems.length,
        itemBuilder: (context, index) {
          final item = _shoppingItems[index];
          return Dismissible(
            key: ValueKey(item.id), // A unique key is crucial for Dismissible
            background: Container(
              color: Colors.red,
              alignment: Alignment.centerRight,
              padding: const EdgeInsets.only(right: 20.0),
              child: const Icon(Icons.delete, color: Colors.white),
            ),
            direction: DismissDirection.endToStart, // Only allow swipe from right to left
            onDismissed: (direction) {
              // Store the item temporarily for undo functionality
              final dismissedItem = item;
              final dismissedItemIndex = _shoppingItems.indexOf(item);

              _removeItem(item.id); // Remove item from our list

              // Show a SnackBar with an undo option
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('${dismissedItem.name} dismissed'),
                  action: SnackBarAction(
                    label: 'UNDO',
                    onPressed: () {
                      setState(() {
                        _shoppingItems.insert(dismissedItemIndex, dismissedItem);
                      });
                    },
                  ),
                ),
              );
            },
            child: Card( // Wrap ListTile in a Card for better visual separation
              margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
              child: ListTile(
                title: Text(
                  item.name,
                  style: TextStyle(
                    decoration: item.isCompleted ? TextDecoration.lineThrough : null,
                    color: item.isCompleted ? Colors.grey : Colors.black,
                  ),
                ),
                trailing: Checkbox(
                  value: item.isCompleted,
                  onChanged: (bool? newValue) {
                    _toggleCompletion(item.id);
                  },
                ),
                onTap: () => _toggleCompletion(item.id), // Tap to toggle completion
              ),
            ),
          );
        },
      ),
      // ... rest of the Scaffold ...

Explanation of Dismissible properties:

  • key: This is mandatory and must be unique for each item. ValueKey(item.id) works perfectly here as our ShoppingListItem has a unique id. Flutter uses this key to correctly identify and animate the widget when it's dismissed or reordered.
  • background: This widget is displayed underneath the child when it is swiped away. We use a red Container with a delete icon.
  • direction: Specifies the directions in which the widget can be dismissed. DismissDirection.endToStart allows swiping only from right to left.
  • onDismissed: This callback is invoked after the widget has been dismissed. Inside this callback, we remove the item from our _shoppingItems list and also display a SnackBar with an "UNDO" option, allowing the user to reverse the dismissal.
  • child: The actual widget that you want to make dismissible. In our case, it's a Card containing a ListTile for each shopping item.

Conclusion

You've now successfully built a functional shopping list widget in Flutter with intuitive swipe-to-delete functionality using the Dismissible widget. This pattern is highly reusable for any list-based application where users need to remove items. You can further enhance this application by adding persistent storage (e.g., using shared_preferences or a database), allowing users to edit items, and implementing more sophisticated state management for larger applications.

The Dismissible widget, combined with ScaffoldMessenger for user feedback, provides a powerful and user-friendly way to manage list items in your Flutter applications.

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