Search and filter are essential features in many apps. Using Bloc, you can build reactive, performant search experiences that debounce user input, handle loading states, and combine multiple filters elegantly. This guide covers everything from simple local filtering to remote API search with real‑time feedback.
Why Bloc for Search & Filter?
- Reactive UI – The list updates automatically as the user types or changes filters.
- Debouncing built‑in – Event transformers let you delay search requests until the user stops typing.
- Centralized state – Search query, filters, results, and loading state live in one place.
- Testable – You can unit‑test the search logic and filtering rules without UI.
- Performance – Optimize rebuilds by listening only to relevant parts of the state.
Local vs Remote Search
| Scenario | Use Case | Implementation |
|---|---|---|
| Local Search | Small, static datasets | Bloc filters an in‑memory list; no network calls. |
| Remote Search | Large datasets or API‑backed | Bloc sends debounced requests to an API; emits loading/error states. |
Example 1: Local Search & Filter
We'll build a product list with a search field and category filter. All data is stored locally, and the bloc filters the list reactively.
Example 2: Remote Search with Debouncing
When searching against an API, you want to avoid sending a request on every keystroke. Use an event transformer with debounce.
Combining Search, Filters, and Pagination
For complex lists, you may need to combine search, multiple filters, and pagination. Keep the state clean and use copyWith to update only changed fields.
Performance Optimizations
- Use
context.selectorBlocSelector– Rebuild only the parts of the UI that depend on specific state fields (e.g., only the list when results change, not the search field). - Debounce input – Avoids flooding the API or filtering on every character.
- Cancel previous requests – With
switchMap(as in debounce example), if a new event arrives while a previous request is in flight, the old one is cancelled. - Use
Equatablefor states – Prevents unnecessary rebuilds when state objects are equal. - Lazy load lists – Implement pagination to load only what the user sees.
Best Practices
- Keep search/filter logic in the bloc – Not scattered in UI widgets.
- Use separate events for each filter change – Makes it easy to track and test.
- Reset pagination when search query or filters change – Emit a fresh state with cleared items.
- Provide visual feedback – Show loading indicators and empty states.
- Throttle or debounce API calls – Use event transformers with
debounceorthrottle. - Cancel ongoing requests – When a new search starts, cancel the previous one to avoid race conditions.
Common Mistakes
- ❌ Calling repository directly in UI – Bypasses bloc, makes testing difficult.
- ❌ No debouncing – Causes too many API calls and poor user experience.
- ❌ Not handling empty state – The list may disappear completely; show a message.
- ❌ Mutating the list instead of emitting a new state – The UI won't update.
- ❌ Rebuilding the entire screen on every keystroke – Use
BlocSelectorto isolate the list from the search input.
What's Next?
Now that you can build reactive search and filter, explore more advanced patterns like infinite scroll pagination and combining with form validation.
Next, explore Form validation with Bloc and Bloc testing.