Debouncer

Debouncing can reduce the number of times your code is executed, but it can also introduce some latency in your user interface.Imagine you have a search bar on a website where users can type in keywords to find articles.
Without debouncing, every single keystroke would send a request to the backend server to fetch results, which could overwhelm the server and cause performance issues.
By using debouncing, the system reduces the number of requests sent to the server,
ensuring better performance and user experience.
The server only receives one request instead of several, and the search results are displayed once the user has finished typing.
// products_viewmodel
class ProductsViewModel {
  final productsRepository = ProductsRepository();
  final products = List<Product>.empty().liveData;

  /// fetch products from server
  Future<void> fetchProducts({String? query}) async {
    final response = await productsRepository.findAll({query: query});
    products.postValue(response.data ?? []);
  }
}
// products_screen
class ProductsScreen extends StatefulWidget {
  const ProductsScreen({Key? key}) : super(key: key);

  @override
  State<ProductsScreen> createState() => ProductsScreenState();
}

class ProductsScreenState extends State<ProductsScreen> {
  // get new debouncer instance 
  final debouncer = Debouncer(delay: const Duration(seconds: 1)); 

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // search text-field
        TextFormField(
          decoration: const InputDecoration(hintText: "Search products by name..."),
          onChanged: (String? value) {
              debouncer(
                () {
                  // filter products from server 
                  viewModel.fetchProducts(value);
                }
              );
          },
        ),

        // ...
      ]
    );
  }
}