Flutter Search Bar from Scratch: Real-Time Filtering ListView (2026)

Almost every real app needs a search bar. A contact list, a product catalogue, a recipe finder — they all share the same pattern: the user types a query, and the list below updates in real time to show only matching results.

The good news is that a Flutter search bar does not need a package. Flutter’s built-in TextField, setState(), and ListView.builder are all you need for a fully working real-time search. Once you understand the two-list pattern at the core of it — an original list you never touch, and a filtered list you display — most search bar bugs become obvious and easy to fix.

This guide builds a complete search bar step by step, explains every design decision, shows how to extend it to API data and objects, covers the built-in Material 3 SearchBar widget, every common beginner bug with fixes, and finishes with interview Q&As on the topic.

📋 Prerequisites

1. The Two-List Pattern — The Core Idea

Before writing any code, understand the one rule that prevents almost every search bar bug: never filter your original list in place. Always keep two separate lists:

List Name convention How it changes Role
Source list_allItemsNever — set once and left aloneThe complete dataset. Never passed to ListView directly.
Display list_filteredItemsEvery keystroke — rebuilt by filtering _allItemsWhat the ListView actually shows. Replaced on every query change.
💡 Why two lists?
The most common search bar bug is filtering _allItems directly. After one search, items are gone permanently — clearing the field cannot restore them because the originals no longer exist. Two lists sidestep this completely: the source list is your safety net, the display list is whatever the current query produces.

Here is the smallest possible working search bar — no extras, just the pattern. Read through the comments before looking at the full version in the next section:

⚠️ Warning: Always wrap ListView in Expanded inside a Column
A ListView inside a Column without Expanded throws a layout error: “RenderFlex children have non-zero flex but incoming height constraints are unbounded.” This is the #1 layout error in beginner search bar code. Expanded tells the ListView to fill all remaining vertical space — add it every single time.

The minimal version works but is missing two things every real search bar needs: a clear button to reset the field, and an empty state message when nothing matches. Here is the complete version with both, plus a TextEditingController to drive the clear button:

💡 Tip: Why use both onChanged AND a controller?
onChanged handles filtering as the user types. The controller handles the clear button — you need _searchController.clear() to programmatically empty the visible text field. Without the controller, pressing clear would reset your filtered list but leave the old query text still sitting in the field.

4. onChanged vs TextEditingController — Which to Use

Flutter provides two standard ways to react to text input. Both work for search bars — the choice depends on what else you need:

Approach How it works Best when
onChangedCallback fired on every keystroke with the current text valueSimple filtering — you only need to react to what the user types
TextEditingControllerObject that can read, set, clear, and listen to the field from codeYou also need to programmatically clear, prefill, or listen to the field

5. Searching a List of Objects (Not Just Strings)

Real apps search lists of objects — products, contacts, posts. The pattern is identical; you just decide which fields to compare. Here is a complete example with a Product class that filters by name, category, or both:

💡 Tip: Normalise both sides before comparing
Always call .toLowerCase() on both the query and the field you are searching — product.name.toLowerCase().contains(query.toLowerCase()). This makes search feel natural: typing “audio”, “Audio”, or “AUDIO” all return the same results. Forgetting this is a very common beginner oversight.

The two-list pattern works identically for API data. Fetch once into _allItems, then filter into _filteredItems exactly like local data. The key mistake to avoid: never try to filter before the API response arrives. Always filter after you have the full dataset loaded:

🚨 Crash warning: Add the http package to pubspec.yaml first
The API example uses the http package. Add it before running:

dependencies:
  http: ^1.2.1

Then run flutter pub get and do a full restart. If you hit build errors after adding the package, our Top 10 Flutter Build Errors guide covers the most common causes.

Many apps put the search bar inside the AppBar itself rather than the body. You can do this by replacing the title with a TextField when search mode is active. Tap the search icon to reveal the field; tap close to dismiss it:

Flutter 3.10+ ships a dedicated SearchBar widget as part of the Material 3 spec. It pairs with SearchAnchor to produce a full animated search-view experience. This is the alternative to the manual TextField approach — less boilerplate, more built-in animation, but less control over styling:

Approach Best for Control level
TextField + setState (sections 2–7)Inline filtering — results appear below the fieldFull — you own all the styling and logic
SearchBar + SearchAnchor (M3)Expanding search view with animated transitionLess — M3 controls the look and animation
💡 Which approach should you use?
Use the TextField + setState pattern (sections 2–7) when you need the search results inline with the rest of your screen content — the most common case for beginners. Use SearchBar + SearchAnchor when you want a dedicated expanding search view with Material 3 animations and less custom styling — common in e-commerce or content discovery apps.

9. Performance Tips for Large Lists

The TextField + setState pattern works well for lists under a few thousand items. For larger datasets, consider these optimisations:

10. Common Beginner Mistakes

Mistake 1: Filtering the source list directly — clears are broken forever

Mistake 2: setState called in the wrong widget — ListView never updates

Mistake 3: Missing Expanded — layout error with ListView inside Column

Mistake 4: Clear button doesn’t work — controller and filter out of sync

Mistake 5: Forgetting to initialise _filteredItems in initState

Mistake 6: Case-sensitive comparison — “apple” finds nothing when item is “Apple”

11. Interview Q&A

Q: What is the easiest way to build a search bar in Flutter?

A: Use a TextField with onChanged, keep a source list and a filtered list, and update the filtered list inside onChanged with setState(). Show the filtered list in a ListView.builder. This is the standard pattern for real-time local filtering and requires no external packages.
Q: Why keep two lists — a source list and a filtered list?

A: Because filtering the source list directly removes items permanently. If you filter _allItems in place, clearing the search cannot restore the full list — those items are gone. Keeping _allItems unchanged and only modifying _filteredItems means you can always rebuild the full list from the original data.
Q: Should I use onChanged or TextEditingController for a search bar?

A: Both are valid. onChanged is simpler — use it when you only need to react to what the user types. TextEditingController is necessary when you also need to programmatically clear the field (for a clear button), prefill it with a value, or listen to changes from outside the widget. For a full search bar with a clear button, use both together: onChanged for filtering and a controller for the clear button.
Q: How do I show a “No results found” message?

A: Check whether _filteredItems.isEmpty and render a fallback widget instead of the ListView. Use a ternary in the Expanded child: if empty show a Center with a message and optional clear button; if not empty show ListView.builder. This is both a UX improvement and a common expectation in interviews about search bar implementations.
Q: How do I filter API data with a search bar?

A: Fetch the full API response into a source list first (_allUsers). Then filter that source list into a display list (_filteredUsers) using the same two-list pattern as local data — onChanged triggers the filter, setState rebuilds the UI. Never try to filter before the API response arrives; always wait for the source list to be populated first.
Q: Why does my ListView not update when I type?

A: Almost always because setState() is being called in the wrong widget. The TextField, the source list, the filtered list, and the ListView.builder must all live in the same StatefulWidget. If the TextField is in a child widget and the list is in a parent or sibling, the setState in the child only rebuilds the child — the list never sees the updated query.
Q: What is the difference between TextField search and the built-in SearchBar widget?

A: The manual TextField approach gives you full control over layout, filtering logic, and styling — the results appear inline with the rest of your screen. Flutter’s built-in SearchBar widget (Material 3, Flutter 3.10+) pairs with SearchAnchor to create an animated expanding search view. Use TextField for inline filtering; use SearchBar + SearchAnchor when you want the full-screen Material 3 search experience with less custom code.
Post Why it’s relevant
Flutter Widgets Explained: Stateless vs StatefulThe entire search bar depends on StatefulWidget and setState() — read this first if either concept is unclear.
Handling User Input: TextField, Forms and ValidationTextField and TextEditingController are at the heart of the search bar — this guide covers both in depth.
Flutter ListView.builder: The Complete Beginner’s GuideThe filtered results are displayed in ListView.builder — this guide covers all its patterns including pagination, pull-to-refresh, and Dismissible.
Flutter Layout Made Easy: Row, Column, Flex and ExpandedExplains Expanded inside Column — essential for understanding and fixing the most common search bar layout error.
Flutter Bottom Navigation Bar: Complete Beginner’s GuideThe Search tab in a nav bar app is the perfect home for this search bar — see how to wire both together.
Top 10 Flutter Build Errors Beginners SeeThe API search example requires the http package — if you hit build errors after adding it, this guide has the fixes.
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply