import 'dart:async'; import 'package:flutter_hooks/flutter_hooks.dart'; /// Used to debounce function calls with the [interval] provided. /// If [maxWaitTime] is provided, the first [run] call as well as the next call since [maxWaitTime] has passed will be immediately executed, even if [interval] is not satisfied. class Debouncer { Debouncer({required this.interval, this.maxWaitTime}); final Duration interval; final Duration? maxWaitTime; Timer? _timer; FutureOr Function()? _lastAction; DateTime? _lastActionTime; Future? _actionFuture; void run(FutureOr Function() action) { _lastAction = action; _timer?.cancel(); if (maxWaitTime != null && // _actionFuture == null && // TODO: should this check be here? (_lastActionTime == null || DateTime.now().difference(_lastActionTime!) > maxWaitTime!)) { _callAndRest(); return; } _timer = Timer(interval, _callAndRest); } Future? drain() { if (_timer != null && _timer!.isActive) { _timer!.cancel(); if (_lastAction != null) { _callAndRest(); } } return _actionFuture; } @pragma('vm:prefer-inline') void _callAndRest() { _lastActionTime = DateTime.now(); final action = _lastAction; _lastAction = null; final result = action!(); if (result is Future) { _actionFuture = result.whenComplete(() { _actionFuture = null; }); } _timer = null; } void dispose() { _timer?.cancel(); _timer = null; _lastAction = null; _lastActionTime = null; _actionFuture = null; } bool get isActive => _actionFuture != null || (_timer != null && _timer!.isActive); } /// Creates a [Debouncer] that will be disposed automatically. If no [interval] is provided, a /// default interval of 300ms is used to debounce the function calls Debouncer useDebouncer({ Duration interval = const Duration(milliseconds: 300), Duration? maxWaitTime, List? keys, }) => use( _DebouncerHook( interval: interval, maxWaitTime: maxWaitTime, keys: keys, ), ); class _DebouncerHook extends Hook { const _DebouncerHook({ required this.interval, this.maxWaitTime, super.keys, }); final Duration interval; final Duration? maxWaitTime; @override HookState> createState() => _DebouncerHookState(); } class _DebouncerHookState extends HookState { late final debouncer = Debouncer( interval: hook.interval, maxWaitTime: hook.maxWaitTime, ); @override Debouncer build(_) => debouncer; @override void dispose() => debouncer.dispose(); @override String get debugLabel => 'useDebouncer'; }