image

05 Jan 2026

9K

35K

Creating a Search Bar Widget with Automatic Filtering in Flutter

Search functionality has become an indispensable feature in modern mobile applications, allowing users to quickly find relevant information within vast datasets. Implementing an efficient and user-friendly search bar with automatic filtering in Flutter can significantly enhance the user experience. This article will guide you through building such a widget, covering data modeling, UI construction, and the core filtering logic.

Why Flutter for Search Functionality?

Flutter, Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, offers a reactive framework that simplifies UI updates based on state changes. Its rich set of customizable widgets and robust state management capabilities make it an excellent choice for developing dynamic search experiences.

Core Concepts

To build our automatic filter search bar, we will leverage a few fundamental Flutter concepts:

  • TextFormField or TextField: For user input, allowing them to type their search query.
  • TextEditingController: To control and listen for changes in the text input field.
  • Stateful Widget: To manage the state of the search query and the list of filtered items.
  • Filtering Logic: A function that processes the original data based on the user's input.
  • ListView.builder: To efficiently display the potentially large list of filtered results.

Step-by-Step Implementation

Let's dive into the code. We'll start with a simple data model and then build the UI and filtering logic.

1. Define Your Data Model

First, let's create a simple class to represent the items we want to search through. For this example, we'll use a list of products.


class Item {
  final String id;
  final String name;
  final String description;

  Item({required this.id, required this.name, required this.description});
}

2. Create an Initial Data Source

Next, we need a list of items to act as our dataset. In a real application, this data might come from an API or a local database.


List allItems = [
  Item(id: '1', name: 'Apple', description: 'A delicious red fruit.'),
  Item(id: '2', name: 'Banana', description: 'A curved yellow fruit.'),
  Item(id: '3', name: 'Orange', description: 'A round citrus fruit.'),
  Item(id: '4', name: 'Strawberry', description: 'A small, red, sweet fruit.'),
  Item(id: '5', name: 'Grape', description: 'Small, round, juicy fruit.'),
  Item(id: '6', name: 'Pineapple', description: 'A large tropical fruit.'),
  Item(id: '7', name: 'Blueberry', description: 'A small, sweet, blue berry.'),
];

3. Build the Search Widget

Now, let's create a StatefulWidget that will house our search bar and the list of filtered results.


import 'package:flutter/material.dart';

// Assuming the Item class and allItems list are defined above in the same file or imported.

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

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

class _SearchPageState extends State {
  // Controller for the search input field
  final TextEditingController _searchController = TextEditingController();
  
  // List to hold the currently filtered items
  List _filteredItems = [];

  @override
  void initState() {
    super.initState();
    // Initialize _filteredItems with all items when the widget is created
    _filteredItems = allItems;
    // Add a listener to the search controller to react to text changes
    _searchController.addListener(_onSearchChanged);
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is disposed
    _searchController.removeListener(_onSearchChanged);
    _searchController.dispose();
    super.dispose();
  }

  // This method is called whenever the text in the search bar changes
  void _onSearchChanged() {
    _filterItems(_searchController.text);
  }

  // The core filtering logic
  void _filterItems(String query) {
    if (query.isEmpty) {
      // If the query is empty, show all items
      setState(() {
        _filteredItems = allItems;
      });
    } else {
      // Otherwise, filter items based on the query
      setState(() {
        _filteredItems = allItems
            .where((item) =>
                item.name.toLowerCase().contains(query.toLowerCase()) ||
                item.description.toLowerCase().contains(query.toLowerCase()))
            .toList();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Automatic Filter Search'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              controller: _searchController,
              decoration: InputDecoration(
                hintText: 'Search...',
                prefixIcon: const Icon(Icons.search),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(8.0),
                  borderSide: BorderSide.none,
                ),
                filled: true,
                fillColor: Colors.grey[200],
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _filteredItems.length,
              itemBuilder: (context, index) {
                final item = _filteredItems[index];
                return Card(
                  margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
                  elevation: 1.0,
                  child: ListTile(
                    title: Text(item.name, style: const TextStyle(fontWeight: FontWeight.bold)),
                    subtitle: Text(item.description),
                    leading: CircleAvatar(child: Text(item.name[0])),
                  ),
                );
              },
            ),
          ),
          if (_filteredItems.isEmpty && _searchController.text.isNotEmpty)
            const Padding(
              padding: EdgeInsets.all(16.0),
              child: Text(
                'No items found matching your search.',
                style: TextStyle(fontSize: 16, color: Colors.grey),
              ),
            ),
        ],
      ),
    );
  }
}

4. Integrate into Your Application

Finally, set your SearchPage as the home screen of your Flutter application.


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

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

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

Explanation of Key Parts

  • TextEditingController _searchController: This controller is attached to the TextField. Its primary role is to listen for text input changes.
  • _filteredItems: A list that holds the items currently visible to the user. It is initialized with all items and updated whenever the search query changes.
  • initState and dispose: These lifecycle methods are crucial. initState sets up the listener on the _searchController, ensuring that our filtering logic runs whenever the user types. dispose cleans up the controller and its listener to prevent memory leaks.
  • _onSearchChanged: This callback simply delegates to _filterItems, passing the current text from the search controller.
  • _filterItems(String query): This is where the magic happens.
    • It checks if the query is empty. If so, it resets _filteredItems to display allItems.
    • If the query is not empty, it uses the where method on allItems to create a new list containing only items whose name or description (converted to lowercase for case-insensitivity) contains the search query.
    • Crucially, setState(() { ... }); is called to notify Flutter that the internal state (_filteredItems) has changed, triggering a rebuild of the widget tree to reflect the new list.
  • TextField: Configured with the _searchController and a visually appealing InputDecoration including a search icon and rounded borders.
  • Expanded and ListView.builder: Expanded ensures the list takes up the remaining vertical space. ListView.builder is used for performance, as it only renders the items that are currently visible on screen.
  • Empty State UI: An if condition checks if _filteredItems is empty and the search bar isn't empty, displaying a user-friendly message.

Refinements and Further Enhancements

  • Debouncing Search Input: For very large datasets or network-based searches, filtering on every keystroke can be inefficient. You can introduce a debounce mechanism (e.g., using a Timer) to wait for a short period after the last keystroke before triggering the filter.
  • Asynchronous Data: If your data comes from an API, you'd integrate FutureBuilder or a state management solution (like Provider, Riverpod, Bloc, GetX) to handle loading states and network requests.
  • Search Scope: You might want to allow users to select specific fields to search within (e.g., "search by name" vs. "search by description").
  • Error Handling: Implement robust error handling for API calls or data parsing issues.
  • Accessibility: Ensure your search widget is accessible, providing appropriate labels and semantic information.

Conclusion

Creating a dynamic search bar with automatic filtering in Flutter is a straightforward process thanks to its reactive nature and powerful widget set. By combining a TextEditingController, a stateful widget, and efficient list rendering with ListView.builder, you can deliver a smooth and responsive search experience that users will appreciate. This foundational example can be extended and customized to fit the specific needs and complexity of any Flutter application.

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