Create a API search func along with textfield , streamController and debounce time of 300ms ?
#api#stream
Answer
Overview
This implements a real-world search pattern: a
text
TextFieldtext
StreamControllerComplete Implementation
dartimport 'dart:async'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class SearchScreen extends StatefulWidget { _SearchScreenState createState() => _SearchScreenState(); } class _SearchScreenState extends State<SearchScreen> { // Stream controller to receive text input events final StreamController<String> _searchController = StreamController<String>(); List<String> _results = []; bool _isLoading = false; StreamSubscription? _subscription; void initState() { super.initState(); _setupSearch(); } void _setupSearch() { _subscription = _searchController.stream .distinct() // Skip if same value as before .debounce(Duration(milliseconds: 300)) // Wait 300ms after last event .listen((query) { if (query.isNotEmpty) { _fetchResults(query); } else { setState(() => _results = []); } }); } Future<void> _fetchResults(String query) async { setState(() => _isLoading = true); try { final response = await http.get( Uri.parse('https://api.example.com/search?q=$query'), ); final data = jsonDecode(response.body) as List; setState(() => _results = data.map((e) => e['name'] as String).toList()); } catch (e) { print('Search error: $e'); } finally { setState(() => _isLoading = false); } } void dispose() { _subscription?.cancel(); _searchController.close(); super.dispose(); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Search')), body: Column( children: [ Padding( padding: EdgeInsets.all(12), child: TextField( decoration: InputDecoration( hintText: 'Search...', prefixIcon: Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), onChanged: (value) => _searchController.sink.add(value), ), ), if (_isLoading) LinearProgressIndicator(), Expanded( child: ListView.builder( itemCount: _results.length, itemBuilder: (context, index) => ListTile(title: Text(_results[index])), ), ), ], ), ); } }
Manual Debounce (Without Stream Extension)
If you prefer a simpler approach without stream extensions:
dartclass _SearchScreenState extends State<SearchScreen> { Timer? _debounceTimer; List<String> _results = []; bool _isLoading = false; void _onSearchChanged(String query) { // Cancel previous timer _debounceTimer?.cancel(); // Start new timer — fires after 300ms of no input _debounceTimer = Timer(Duration(milliseconds: 300), () { if (query.isNotEmpty) _fetchResults(query); }); } void dispose() { _debounceTimer?.cancel(); super.dispose(); } Future<void> _fetchResults(String query) async { setState(() => _isLoading = true); try { final response = await http.get( Uri.parse('https://dummyjson.com/products/search?q=$query'), ); final data = jsonDecode(response.body); setState(() { _results = (data['products'] as List) .map((p) => p['title'] as String) .toList(); }); } finally { setState(() => _isLoading = false); } } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Search')), body: Column( children: [ Padding( padding: EdgeInsets.all(12), child: TextField( decoration: InputDecoration( hintText: 'Search products...', prefixIcon: Icon(Icons.search), suffixIcon: _isLoading ? Padding( padding: EdgeInsets.all(12), child: SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ), ) : null, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), onChanged: _onSearchChanged, ), ), Expanded( child: ListView.builder( itemCount: _results.length, itemBuilder: (context, index) => ListTile( leading: Icon(Icons.search, color: Colors.blue), title: Text(_results[index]), ), ), ), ], ), ); } }
How Debounce Works
textUser types: F → Fl → Flu → Flut → Flutt → Flutter ↑ ↑ ↑ ↑ ↑ ↑ Timer Timer Timer Timer Timer Timer (300ms) reset reset reset reset reset fires → API call!
Each keystroke resets the timer. Only when the user stops typing for 300ms does the API call fire.
Why StreamController + Debounce?
| Approach | Debounce | Cancel in-flight | Reactive |
|---|---|---|---|
text text | ✅ Manual | ✅ Manual | ❌ |
text | ✅ Reactive | ✅ text | ✅ |
text text | ✅ Built-in | ✅ | ✅ Best |
Best Production Approach: Use
'stextrxdartwithtextBehaviorSubjectfor the cleanest reactive search implementation.text.debounceTime()