2023-08-27 05:07:35 +00:00
import ' dart:async ' ;
2024-01-15 15:26:13 +00:00
import ' package:flutter_hooks/flutter_hooks.dart ' ;
2023-08-27 05:07:35 +00:00
2024-01-15 15:26:13 +00:00
/// Used to debounce function calls with the [interval] provided.
2024-12-05 02:33:46 +05:30
/// 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.
2024-01-15 15:26:13 +00:00
class Debouncer {
2024-12-05 02:33:46 +05:30
Debouncer ( { required this . interval , this . maxWaitTime } ) ;
2024-01-15 15:26:13 +00:00
final Duration interval ;
2024-12-05 02:33:46 +05:30
final Duration ? maxWaitTime ;
2023-08-27 05:07:35 +00:00
Timer ? _timer ;
2024-01-15 15:26:13 +00:00
FutureOr < void > Function ( ) ? _lastAction ;
2024-12-05 02:33:46 +05:30
DateTime ? _lastActionTime ;
Future < void > ? _actionFuture ;
2023-08-27 05:07:35 +00:00
2024-01-15 15:26:13 +00:00
void run ( FutureOr < void > Function ( ) action ) {
_lastAction = action ;
2023-08-27 05:07:35 +00:00
_timer ? . cancel ( ) ;
2024-12-05 02:33:46 +05:30
if ( maxWaitTime ! = null & &
// _actionFuture == null && // TODO: should this check be here?
2025-07-25 08:07:22 +05:30
( _lastActionTime = = null | | DateTime . now ( ) . difference ( _lastActionTime ! ) > maxWaitTime ! ) ) {
2024-12-05 02:33:46 +05:30
_callAndRest ( ) ;
return ;
}
2024-01-15 15:26:13 +00:00
_timer = Timer ( interval , _callAndRest ) ;
2023-08-27 05:07:35 +00:00
}
2024-12-05 02:33:46 +05:30
Future < void > ? drain ( ) {
2025-08-06 11:05:49 -04:00
final timer = _timer ;
if ( timer ! = null & & timer . isActive ) {
timer . cancel ( ) ;
2024-12-05 02:33:46 +05:30
if ( _lastAction ! = null ) {
_callAndRest ( ) ;
}
}
return _actionFuture ;
}
@ pragma ( ' vm:prefer-inline ' )
2023-08-27 05:07:35 +00:00
void _callAndRest ( ) {
2024-12-05 02:33:46 +05:30
_lastActionTime = DateTime . now ( ) ;
final action = _lastAction ;
_lastAction = null ;
final result = action ! ( ) ;
if ( result is Future ) {
_actionFuture = result . whenComplete ( ( ) {
_actionFuture = null ;
} ) ;
}
2023-08-27 05:07:35 +00:00
_timer = null ;
}
void dispose ( ) {
_timer ? . cancel ( ) ;
_timer = null ;
2024-01-15 15:26:13 +00:00
_lastAction = null ;
2024-12-05 02:33:46 +05:30
_lastActionTime = null ;
_actionFuture = null ;
2023-08-27 05:07:35 +00:00
}
2024-12-05 02:33:46 +05:30
2025-07-25 08:07:22 +05:30
bool get isActive = > _actionFuture ! = null | | ( _timer ! = null & & _timer ! . isActive ) ;
2023-08-27 05:07:35 +00:00
}
2024-01-15 15:26:13 +00:00
/// 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 ) ,
2024-12-05 02:33:46 +05:30
Duration ? maxWaitTime ,
2024-01-15 15:26:13 +00:00
List < Object ? > ? keys ,
2025-07-29 00:34:03 +05:30
} ) = > use ( _DebouncerHook ( interval: interval , maxWaitTime: maxWaitTime , keys: keys ) ) ;
2024-01-15 15:26:13 +00:00
class _DebouncerHook extends Hook < Debouncer > {
2025-07-29 00:34:03 +05:30
const _DebouncerHook ( { required this . interval , this . maxWaitTime , super . keys } ) ;
2024-01-15 15:26:13 +00:00
final Duration interval ;
2024-12-05 02:33:46 +05:30
final Duration ? maxWaitTime ;
2024-01-15 15:26:13 +00:00
@ override
HookState < Debouncer , Hook < Debouncer > > createState ( ) = > _DebouncerHookState ( ) ;
}
class _DebouncerHookState extends HookState < Debouncer , _DebouncerHook > {
2025-07-29 00:34:03 +05:30
late final debouncer = Debouncer ( interval: hook . interval , maxWaitTime: hook . maxWaitTime ) ;
2024-01-15 15:26:13 +00:00
@ override
Debouncer build ( _ ) = > debouncer ;
@ override
void dispose ( ) = > debouncer . dispose ( ) ;
@ override
String get debugLabel = > ' useDebouncer ' ;
}