image

08 Jan 2026

9K

35K

Building a Multi-Select List Widget with Search in Flutter

In modern applications, users often need to select multiple items from a list, especially when dealing with large datasets. A simple list of checkboxes can become cumbersome and inefficient if the list contains many items. Adding a search capability significantly enhances the user experience, allowing users to quickly find and select specific items. This article will guide you through creating a professional, reusable multi-select list widget with integrated search functionality in Flutter.

Why a Custom Multi-Select Widget?

  • Improved UX for Large Lists: A search bar prevents endless scrolling and allows immediate access to desired items.
  • Reusability: Encapsulate complex logic and UI into a single widget that can be used across different parts of your application.
  • Customization: Tailor the appearance and behavior precisely to your application's design system.
  • Performance: Efficiently filter items without re-rendering the entire list.

Core Components and Approach

Our custom widget will be a StatefulWidget to manage its internal state, including the search query, the filtered list of items, and the set of currently selected items. It will consist of:

  • A TextField for the search input.
  • An Expanded ListView.builder to efficiently display the filtered items.
  • CheckboxListTile for each item to handle selection.
  • A callback mechanism to notify the parent widget about changes in the selected items.

Step-by-Step Implementation

1. Define the Data Model

First, let's define a simple data model for the items we want to display and select. For our example, each item will have a unique ID and a name.


class MyItem {
  final String id;
  final String name;

  MyItem({required this.id, required this.name});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is MyItem && runtimeType == other.runtimeType && id == other.id;

  @override
  int get hashCode => id.hashCode;
}

Note the overridden operator == and hashCode. This is crucial when storing MyItem objects in a Set, ensuring that items with the same ID are considered equal, which is fundamental for correctly managing selections.

2. Create the MultiSelectSearchList Widget

This will be our main reusable widget. It takes the full list of items, initially selected items, and a callback function for when selections change.


import 'package:flutter/material.dart';

// Assume MyItem class is defined above or imported

class MultiSelectSearchList extends StatefulWidget {
  final List items;
  final Set initialSelectedItems;
  final ValueChanged> onSelectionChanged;
  final String title;

  const MultiSelectSearchList({
    Key? key,
    required this.items,
    this.initialSelectedItems = const {},
    required this.onSelectionChanged,
    this.title = 'Select Items',
  }) : super(key: key);

  @override
  _MultiSelectSearchListState createState() => _MultiSelectSearchListState();
}

class _MultiSelectSearchListState extends State {
  late List _filteredItems;
  late Set _selectedItems;
  final TextEditingController _searchController = TextEditingController();

  @override
  void initState() {
    super.initState();
    // Initialize filtered items with all available items
    _filteredItems = List.from(widget.items);
    // Initialize selected items from the initial list provided by the parent
    _selectedItems = Set.from(widget.initialSelectedItems);
    // Add a listener to the search controller to filter items as the user types
    _searchController.addListener(_onSearchChanged);
  }

  @override
  void dispose() {
    _searchController.removeListener(_onSearchChanged);
    _searchController.dispose();
    super.dispose();
  }

  // This method is called whenever the search text changes
  void _onSearchChanged() {
    setState(() {
      final query = _searchController.text.toLowerCase();
      _filteredItems = widget.items.where((item) {
        return item.name.toLowerCase().contains(query);
      }).toList();
    });
  }

  // Toggles the selection status of an item
  void _toggleSelection(MyItem item) {
    setState(() {
      if (_selectedItems.contains(item)) {
        _selectedItems.remove(item);
      } else {
        _selectedItems.add(item);
      }
    });
    // Notify the parent widget about the selection change
    widget.onSelectionChanged(_selectedItems);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Search TextField
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: TextField(
            controller: _searchController,
            decoration: InputDecoration(
              labelText: 'Search',
              hintText: 'Search items...',
              prefixIcon: Icon(Icons.search),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(10),
              ),
            ),
          ),
        ),
        // Expanded list of items
        Expanded(
          child: ListView.builder(
            itemCount: _filteredItems.length,
            itemBuilder: (context, index) {
              final item = _filteredItems[index];
              return CheckboxListTile(
                title: Text(item.name),
                value: _selectedItems.contains(item), // Check if item is selected
                onChanged: (bool? isSelected) {
                  _toggleSelection(item); // Toggle selection on tap
                },
              );
            },
          ),
        ),
      ],
    );
  }
}

3. Example Usage in a Parent Widget

To demonstrate how to use this widget, let's integrate it into a simple Flutter application. The parent widget will hold the master list of all available items and the current set of selected items, updating its state when the multi-select widget notifies it.


import 'package:flutter/material.dart';

// Assuming MyItem and MultiSelectSearchList are defined above or imported

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multi-Select Search Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  // A list of all items that can be selected
  final List _allAvailableItems = List.generate(
    50, // Generate 50 dummy items
    (index) => MyItem(id: 'id_$index', name: 'Item ${index + 1}'),
  );

  // The set of items currently selected by the user
  Set _currentlySelectedItems = {};

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Multi-Select List Demo'),
      ),
      body: Column(
        children: [
          // Display currently selected items
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Selected Items:',
                  style: Theme.of(context).textTheme.headline6,
                ),
                SizedBox(height: 8),
                Wrap(
                  spacing: 8.0,
                  children: _currentlySelectedItems
                      .map((item) => Chip(label: Text(item.name)))
                      .toList(),
                ),
                SizedBox(height: 20),
              ],
            ),
          ),
          // Our MultiSelectSearchList widget
          Expanded(
            child: MultiSelectSearchList(
              items: _allAvailableItems,
              initialSelectedItems: _currentlySelectedItems,
              onSelectionChanged: (selectedItems) {
                // Update the parent's state with the new selections
                setState(() {
                  _currentlySelectedItems = selectedItems;
                });
                // Optional: Print selected items to console
                print('Selected items updated: ${_currentlySelectedItems.map((e) => e.name).join(', ')}');
              },
            ),
          ),
        ],
      ),
    );
  }
}

Conclusion

You have successfully built a powerful and reusable Multi-Select List Widget with search functionality in Flutter. This widget significantly enhances the user experience when dealing with extensive lists, providing quick navigation and clear selection feedback. By encapsulating this logic and UI, you create a component that can be easily integrated and customized across your application.

Further enhancements could include:

  • Adding custom styling options for the search bar, list tiles, and selection indicators.
  • Implementing asynchronous data loading or pagination for extremely large datasets.
  • Integrating with more advanced state management solutions like Provider or Bloc for complex application architectures.
  • Adding "Select All" / "Deselect All" options.

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