LCOV - code coverage report
Current view: top level - src - debouncer.dart Coverage Total Hit
Test: lcov.info Lines: 93.5 % 31 29
Test Date: 2026-04-18 12:40:21 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : import 'package:flutter/foundation.dart';
       3              : import 'debouncer_config.dart';
       4              : import 'strategy/debounce_strategy.dart';
       5              : import 'strategy/trailing_edge_strategy.dart';
       6              : import 'utils/debouncer_logger.dart';
       7              : import 'utils/disposable.dart';
       8              : import 'utils/lifecycle_hooks.dart';
       9              : 
      10              : /// A flexible, strategy-based debouncer for Flutter and Dart.
      11              : ///
      12              : /// [Debouncer] delays executing an action until a specified [delay] has
      13              : /// passed since the last invocation. The firing behaviour is controlled
      14              : /// by a pluggable [DebouncerStrategy].
      15              : ///
      16              : /// ## Basic usage
      17              : /// ```dart
      18              : /// final _debouncer = Debouncer(delay: Duration(milliseconds: 300));
      19              : ///
      20              : /// void onSearchChanged(String query) {
      21              : ///   _debouncer.run(() => _fetchResults(query));
      22              : /// }
      23              : ///
      24              : /// @override
      25              : /// void dispose() {
      26              : ///   _debouncer.dispose();
      27              : ///   super.dispose();
      28              : /// }
      29              : /// ```
      30              : ///
      31              : /// ## Leading-edge (fires immediately, then locks)
      32              : /// ```dart
      33              : /// final _debouncer = Debouncer(
      34              : ///   delay: Duration(milliseconds: 500),
      35              : ///   strategy: LeadingEdgeStrategy(),
      36              : /// );
      37              : /// ```
      38              : ///
      39              : /// ## With logging
      40              : /// ```dart
      41              : /// final _debouncer = Debouncer(
      42              : ///   delay: Duration(milliseconds: 300),
      43              : ///   logger: DebouncerLogger(level: DebouncerLogLevel.verbose),
      44              : /// );
      45              : /// ```
      46              : class Debouncer with Disposable {
      47              :   /// The active configuration (delay, label, edge flags).
      48              :   final DebouncerConfig config;
      49              : 
      50              :   /// The strategy that decides when and how the action fires.
      51              :   final DebouncerStrategy strategy;
      52              : 
      53              :   /// Optional logger for debugging debounce events.
      54              :   final DebouncerLogger? logger;
      55              : 
      56              :   /// Optional lifecycle hooks (onFire, onCancel, onDispose).
      57              :   final LifecycleHooks? hooks;
      58              : 
      59              :   Timer? _timer;
      60              : 
      61              :   /// Creates a [Debouncer] with the given [delay] and optional overrides.
      62              :   ///
      63              :   /// - [delay]    — how long to wait after the last call. Default: 300 ms.
      64              :   /// - [DebouncerStrategy] — firing behaviour. Default: [TrailingEdgeStrategy].
      65              :   /// - [logger]   — attach a [DebouncerLogger] to trace events.
      66              :   /// - [hooks]    — lifecycle callbacks (onFire, onCancel, onDispose).
      67              :   /// - [label]    — a debug label shown in log output.
      68            1 :   Debouncer({
      69              :     Duration delay = const Duration(milliseconds: 300),
      70              :     DebouncerStrategy? strategy,
      71              :     this.logger,
      72              :     this.hooks,
      73              :     String? label,
      74            1 :   })  : config = DebouncerConfig(delay: delay, debugLabel: label),
      75              :         strategy = strategy ?? const TrailingEdgeStrategy();
      76              : 
      77              : 
      78              : 
      79              :   /// Creates a [Debouncer] directly from a [DebouncerConfig].
      80            1 :   Debouncer.fromConfig(
      81              :       this.config, {
      82              :         DebouncerStrategy? strategy,
      83              :         this.logger,
      84              :         this.hooks,
      85              :       }) : strategy = strategy ?? const TrailingEdgeStrategy();
      86              : 
      87              :   /// Whether a call is currently pending (timer is running).
      88            8 :   bool get isPending => _timer != null && _timer!.isActive;
      89              : 
      90              :   /// Schedules [action] to run according to the active [DebouncerStrategy].
      91              :   ///
      92              :   /// Each call resets the internal timer (for trailing-edge strategies).
      93              :   /// Throws a [StateError] if called after [dispose].
      94            2 :   void run(VoidCallback action) {
      95            2 :     assertNotDisposed('run');
      96              : 
      97            2 :     final bool hadPendingTimer = isPending;
      98              : 
      99            4 :     strategy.execute(
     100            2 :           () {
     101            5 :         logger?.logFire(config.debugLabel);
     102            4 :         hooks?.onFire?.call();
     103            2 :         action();
     104              :       },
     105            2 :       config,
     106            2 :       _timer,
     107            2 :           (t) {
     108            2 :         _timer = t;
     109              :         if (t != null) {
     110            7 :           logger?.logReset(config.debugLabel, config.delay);
     111              :         }
     112              :       },
     113              :     );
     114              : 
     115              :     if (hadPendingTimer) {
     116            5 :       logger?.logCancel(config.debugLabel);
     117            4 :       hooks?.onCancel?.call();
     118              :     }
     119              :   }
     120              : 
     121              :   /// Cancels any pending action without executing it.
     122            1 :   void cancel() {
     123            1 :     if (isPending) {
     124            2 :       _timer?.cancel();
     125            1 :       _timer = null;
     126            4 :       logger?.logCancel(config.debugLabel);
     127            1 :       hooks?.onCancel?.call();
     128              :     }
     129              :   }
     130              : 
     131              :   /// Cancels any pending timer and marks this instance as disposed.
     132              :   ///
     133              :   /// Always call [dispose] in [State.dispose] to prevent timer leaks.
     134            2 :   @override
     135              :   void onDispose() {
     136            4 :     _timer?.cancel();
     137            2 :     _timer = null;
     138            4 :     hooks?.onDispose?.call();
     139              :   }
     140              : 
     141            0 :   @override
     142              :   String toString() =>
     143            0 :       'Debouncer(config: $config, isPending: $isPending, disposed: $isDisposed)';
     144              : }
        

Generated by: LCOV version 2.4-0