A search bar is one of the most essential UI components in modern applications. It’s the user’s primary tool for finding content quickly and efficiently. A well-implemented search bar can dramatically improve user experience, while a poor one can lead to frustration.
In this guide, we’ll cover everything you need to know to build a powerful and responsive search bar in Flutter. We’ll start with a simple TextField
, move on to filtering a list of data in real-time, and finally, explore Flutter’s built-in SearchDelegate
for a more advanced, full-screen search experience.
The Foundation: Building a Search UI with TextField
At its core, a search bar is just a styled TextField
widget. We can place it directly in the AppBar
for easy access. Let’s start by creating a visually appealing search input field.
The key is the decoration
property of the TextField
, which allows us to customize its appearance with hints, icons, and borders.
Dart
import 'package:flutter/material.dart';
class SimpleSearchBar extends StatelessWidget {
const SimpleSearchBar({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Search'),
// The search area here
bottom: PreferredSize(
preferredSize: const Size.fromHeight(60.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
hintText: 'Search for something...',
// Add a clear button to the search bar
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => print("Clear"),
),
// Add a search icon to the search bar
prefixIcon: IconButton(
icon: const Icon(Icons.search),
onPressed: () {
// Perform the search here
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
),
),
),
),
),
),
body: const Center(
child: Text('Your content goes here'),
),
);
}
}
DartIn this snippet, we use a TextField
inside the bottom
property of the AppBar
. We’ve added:
hintText
: To guide the user.prefixIcon
: The classic search magnifying glass.suffixIcon
: A button to clear the input.border
: A rounded border to give it a modern look.
Making It Functional: Filtering a List
A search bar isn’t useful until it can filter data. Let’s create a stateful widget that filters a list of strings based on the user’s input.
We’ll need two lists: one to hold all the original items and another to hold the filtered items that match the search query. We’ll use a TextEditingController
to listen to changes in the search field.
Dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: SearchPage(),
);
}
}
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
final TextEditingController _searchController = TextEditingController();
List<String> _allItems = [
'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon'
];
List<String> _filteredItems = [];
@override
void initState() {
super.initState();
_filteredItems = _allItems;
_searchController.addListener(_filterItems);
}
void _filterItems() {
final query = _searchController.text.toLowerCase();
setState(() {
_filteredItems = _allItems.where((item) {
return item.toLowerCase().contains(query);
}).toList();
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _searchController,
style: const TextStyle(color: Colors.white),
cursorColor: Colors.white,
decoration: const InputDecoration(
hintText: 'Search...',
hintStyle: TextStyle(color: Colors.white54),
border: InputBorder.none,
),
),
),
body: ListView.builder(
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_filteredItems[index]),
);
},
),
);
}
}
DartHow It Works:
initState
: We initialize_filteredItems
with all items and add a listener to our_searchController
._filterItems
: This function is called every time the text in the search bar changes. It filters_allItems
based on whether an item contains the search query (converted to lowercase for case-insensitive matching).setState
: We callsetState
to rebuild the UI with the_filteredItems
list, updating theListView
in real-time.dispose
: It’s crucial to dispose of theTextEditingController
to prevent memory leaks.
The Professional Approach: Using SearchDelegate
For a more robust and conventional search experience, Flutter provides the SearchDelegate
class. It’s designed to manage a dedicated search screen that overlays your current UI, complete with animations and platform-adaptive back buttons.
Using SearchDelegate
is the recommended approach for a polished search feature.
Step 1: Create a Custom Search Delegate
First, create a class that extends SearchDelegate
. You’ll need to override four key methods:
Dart
import 'package:flutter/material.dart';
class CustomSearchDelegate extends SearchDelegate {
// Dummy list
final List<String> searchTerms = [
'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon'
];
// To clear the search text
@override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
// To exit the search screen
@override
Widget? buildLeading(BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
// To show results based on the search query
@override
Widget buildResults(BuildContext context) {
List<String> matchQuery = [];
for (var fruit in searchTerms) {
if (fruit.toLowerCase().contains(query.toLowerCase())) {
matchQuery.add(fruit);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: (context, index) {
var result = matchQuery[index];
return ListTile(
title: Text(result),
);
},
);
}
// To show suggestions while the user is typing
@override
Widget buildSuggestions(BuildContext context) {
List<String> matchQuery = [];
for (var fruit in searchTerms) {
if (fruit.toLowerCase().contains(query.toLowerCase())) {
matchQuery.add(fruit);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: (context, index) {
var result = matchQuery[index];
return ListTile(
title: Text(result),
onTap: () {
query = result;
showResults(context);
},
);
},
);
}
}
DartbuildActions
: Defines actions for theAppBar
(e.g., a clear button).buildLeading
: Defines the leading icon/widget (e.g., a back button to close the search).buildResults
: Builds the UI to display after the user submits a search.buildSuggestions
: Shows suggestions in real-time as the user types.
Step 2: Launch the Search Delegate
Finally, trigger the search from an IconButton
in your main AppBar
.
Dart
Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: CustomSearchDelegate(),
);
},
)
],
),
// ... rest of your scaffold
)
DartThe showSearch
function takes care of pushing a new route and displaying your CustomSearchDelegate
.
Building an intuitive search feature is a huge step toward a great user experience. Whether you choose a simple TextField
or the more advanced SearchDelegate
, the key is to provide fast, relevant results.