debouncing 1.1.0 copy "debouncing: ^1.1.0" to clipboard
debouncing: ^1.1.0 copied to clipboard

A lightweight Flutter package providing a simple [debounce, throttle, back off] utility to function calls, ideal for optimizing performance in event-driven application.

Debounce, Throttle, BackOff #

A lightweight Flutter package providing a simple [debounce, throttle, back off] utility to function calls, ideal for optimizing performance in event-driven application.

Demo #

Features #

  • Simple and easy-to-use functionality with (ThrottleMixins, DebounceMixins). Mixins help you to close Throttle and Debounce automatically, and it's not necessary to close it manually.
  • When using debounceTransform or throttleTransform in Bloc, you can now cancel the previous event using ResettableEvent.
  • Configurable [duration, leading, trailing] in Debounce and Throttle.
  • Configurable [percentageRandomization, initialDelay, maxDelay, maxAttempts, retryIf] in BackOff.
  • Ideal for Flutter applications to handle rapid user inputs (e.g., search fields, button clicks).

Installation #

Add the following to your pubspec.yaml file:

dependencies:
  debouncing: ^[latest_version]

Then run:

flutter pub get

Usage Example #

event transformers for [Bloc] #

import 'package:debouncing/debouncing.dart';

/// 👉 Usual example of event:
sealed class MyEvent {
  const MyEvent();
}

// final class OnSearchEvent extends MyEvent { 👉 // ⚠️ you can write without `ResettableEvent`
final class OnSearchEvent extends MyEvent with ResettableEvent { 👉 // ⚠️ ... with ResettableEvent, ... extends ResettableEvent, ... implements ResettableEvent
  @override
  final bool resetOnlyPreviousEvent;
  final String text;

  const OnSearchEvent({required this.text, this.resetOnlyPreviousEvent = false});
}

// final class OnScrollEvent extends MyEvent { 👉 // ⚠️ you can write without `ResettableEvent`
final class OnScrollEvent extends MyEvent with ResettableEvent { 👉 // ⚠️ ... with ResettableEvent, ... extends ResettableEvent, ... implements ResettableEvent
  @override
  final bool resetOnlyPreviousEvent;

  const OnScrollEvent({this.resetOnlyPreviousEvent = false});
}

----------------------------------------------------------------------------------------------------

/// 👉 Freezed example of event:
@freezed
abstract class MyEvent with _$MyEvent {

  // const factory MyEvent.onSearchEvent({ 👉 // ⚠️ you can write without `@Implements<ResettableEvent>()`
  @Implements<ResettableEvent>()
  const factory MyEvent.onSearchEvent({
    required String text,
    @Default(false) bool resetOnlyPreviousEvent,
  }) = OnSearchEvent;

  // const factory MyEvent.onScroll({ 👉 // ⚠️ you can write without `@Implements<ResettableEvent>()`
  @Implements<ResettableEvent>()
  const factory MyEvent.onScroll({
    @Default(false) bool resetOnlyPreviousEvent,
  }) = OnScrollEvent;
}

----------------------------------------------------------------------------------------------------

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyStateInitial()) {
    on<OnSearchEvent>(
      _onSearch,
      transformer: debounceTransform(
        delay: const Duration(milliseconds: 300),
        leading: false,
        trailing: true,
      ),
    );
    on<OnScrollEvent>(
      _onScroll,
      transformer: throttleTransform(
        interval: const Duration(milliseconds: 300),
        leading: true,
        trailing: false,
      ),
    );
  }

  void _onScroll(OnScroll event, Emitter emit) {
    // ...
  }

  void _onSearch(OnTextChanged event, Emitter emit) {
    // ...
  }
}

mixin for [Bloc and Cubit] #

import 'package:debouncing/debouncing.dart';

class MyBloc extends Bloc<MyEvent, MyState> with DebounceMixin, ThrottleMixin {
  MyBloc() : super(MyState()) {
    on<OnScroll>(_onScroll);
    on<OnTextChanged>(_onTextChanged);
  }

  void _onScroll(OnScroll event, Emitter emit) {
    throttle(() {
      emit(MyNewState());
      // ...
    });
  }

  void _onSearch(OnTextChanged event, Emitter emit) {
    debounce(() {
      emit(MyNewState());
      // ...
    });
  }
}

-------------------------------------------------------------------------------------------

class MyCubit extends Cubit<MyState> with DebounceMixin, ThrottleMixin {
  MyCubit() : super(const MyState());

  void onSearch(String text) {
    debounce(() {
      emit(MyNewState());
      // ...
    });
  }

  void onScroll() {
    throttle(() {
      emit(MyNewState());
      // ...
    });
  }
}

-------------------------------------------------------------------------------------------

/// 👉 You can write mixin for [Bloc] and [Cubit] like this.
/// If you need full version mixin with [Documentation] and [Usage example] for your project,
/// you can take it from file (lib/src/debouncing/mixins/debounce_bloc_and_cubit_mixin.dart)

mixin DebounceMixin<T> on BlocBase<T> {
  @protected
  @visibleForTesting
  DebounceParams get debounceParams => const DebounceParams();

  @protected
  @nonVirtual
  @visibleForTesting
  late final Debounce debounce = Debounce(
    delay: debounceParams.delay,
    leading: debounceParams.leading,
    trailing: debounceParams.trailing,
  );

  @override
  @protected
  @mustCallSuper
  Future<void> close() {
    debounce.dispose();
    return super.close();
  }
}

/// 👉 You can write mixin for [Bloc] and [Cubit] like this.
/// If you need full version mixin with [Documentation] and [Usage example] for your project,
/// you can take it from file (lib/src/throttling/mixins/throttle_bloc_and_cubit_mixin.dart)

mixin ThrottleMixin<T> on BlocBase<T> {
  @protected
  @visibleForTesting
  ThrottleParams get throttleParams => const ThrottleParams();

  @protected
  @nonVirtual
  @visibleForTesting
  late final Throttle throttle = Throttle(
    interval: throttleParams.interval,
    leading: throttleParams.leading,
    trailing: throttleParams.trailing,
  );

  @override
  @protected
  @mustCallSuper
  Future<void> close() {
    throttle.dispose();
    return super.close();
  }
}

mixin for [Provider] and other classes inherited from 'ChangeNotifier' #

import 'package:debouncing/debouncing.dart';

class MyProvider with ChangeNotifier, ThrottleNotifierMixin, DebounceNotifierMixin {

  void onScroll() {
    throttle(() {
      // ...
      notifyListeners();
    });
  }

  void onSearch(String query) {
    debounce(() {
      // ...
      notifyListeners();
    });
  }
}

mixin for [StatefulWidget] #

import 'package:flutter/material.dart';
import 'package:debouncing/debouncing.dart';

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with ThrottleStateMixin, DebounceStateMixin {

  void onScroll() {
    throttle(() {
      // ...
    });
  }

  void _onSearch(String value) {
    debounce(() {
      // ...
    });
  }

  @override
  Widget build(BuildContext context) {
    // ...
  }
}

mixin for [GetX] #

import 'package:debouncing/debouncing.dart';
import 'package:get/get.dart' hide debounce;

class MyController extends GetxController with DebounceGetXMixin, ThrottleGetXMixin {

  void onSearch(String query) {
    debounce(() {
      // ...
      update();
    });
  }

  void onScroll(String query) {
    throttle(() {
      // ...
      update();
    });
  }

}

/// 👉 You can write mixin for [GetxController] and [GetxService] like this.
/// If you need full version mixin with [Documentation] and [Usage example] for your project,
/// you can take it from file (lib/src/debouncing/mixins/debounce_getx_mixin.dart)

mixin DebounceGetXMixin on GetLifeCycleBase {
  @protected
  @visibleForTesting
  DebounceParams get debounceParams => const DebounceParams();

  @protected
  @nonVirtual
  @visibleForTesting
  late final Debounce debounce = Debounce(
    delay: debounceParams.delay,
    leading: debounceParams.leading,
    trailing: debounceParams.trailing,
  );

  @override
  @protected
  @mustCallSuper
  void onClose() {
    debounce.dispose();
    super.onClose();
  }
}

/// 👉 You can write mixin for [GetxController] and [GetxService] like this.
/// If you need full version mixin with [Documentation] and [Usage example] for your project,
/// you can take it from file (lib/src/throttling/mixins/throttle_getx_mixin.dart)

mixin ThrottleGetXMixin on GetLifeCycleBase {
  @protected
  @visibleForTesting
  ThrottleParams get throttleParams => const ThrottleParams();
  
  @protected
  @nonVirtual
  @visibleForTesting
  late final Throttle throttle = Throttle(
    interval: throttleParams.interval,
    leading: throttleParams.leading,
    trailing: throttleParams.trailing,
  );
  
  @override
  @protected
  @mustCallSuper
  void onClose() {
    throttle.dispose();
    super.onClose();
  }
}

Reference #

Debounce, Throttle and EventTransformers [debounceTransform, throttleTransform] for Bloc #

///     source.debounceTransform(delay: Duration(seconds: 1), leading: false, trailing: true); ✅ Good!
///     Config: leading: false, trailing: true
///     Input:  1-2-3---4---5-6-|
///     Output: ------3---4-----6|
///
///     source.debounceTransform(delay: Duration(seconds: 1), leading: true, trailing: false); ✅ Good!
///     Config: leading: true, trailing: false
///     Input:  1-2-3---4---5-6-|
///     Output: 1-------4---5---|
///
///     source.debounceTransform(delay: Duration(seconds: 1), leading: true, trailing: true); ✅ Good!
///     Config: leading: true, trailing: true
///     Input:  1-2-3---4---5-6-|
///     Output: 1-----3-4---5---6|
///
///     source.debounceTransform(delay: Duration(seconds: 1), leading: false, trailing: false); ❌ Bad! Output empty!
///     Config: leading: false, trailing: false
///     Input:  1-2-3---4---5-6-|
///     Output: ----------------|

    on<OnSearchEvent>(
      _onSearchEvent,
      transformer: debounceTransform(
        delay: const Duration(seconds: 1),
        leading: false,
        trailing: true,
      ),
    );

-----------------------------------------------------------------------------------------------------------------------

///     source.throttleTransform(interval: const Duration(seconds: 6), leading: true, trailing: false); ✅ Good!
///     Config: leading: true, trailing: false
///     Input:  1-2-3---4-5-6---7-8-|
///     Output: 1-------4-------7---|
///
///     source.throttleTransform(interval: const Duration(seconds: 6), leading: false, trailing: true); ✅ Good!
///     Config: leading: false, trailing: true
///     Input:  1-2-3---4-5----6--|
///     Output: ------3-----5-----6|
///
///     source.throttleTransform(interval: const Duration(seconds: 6),  leading: true, trailing: true); ✅ Good!
///     Config: leading: true, trailing: true
///     Input:  1-2-----3-----4|
///     Output: 1-----2-----3--|
///
///     source.throttleTransform(interval: const Duration(seconds: 6), leading: false, trailing: false); ❌ Bad! Output empty!
///     Config: leading: false, trailing: false
///     Input:  1-2-3---4-5----6--|
///     Output: -------------------|

    on<OnScrollEvent>(
      _onScrollEvent,
      transformer: throttleTransform(
        interval: const Duration(seconds: 6),
        leading: true,
        trailing: false,
      ),
    );

///     final _debounce = Debounce(delay: Duration(seconds: 1), leading: false, trailing: true); ✅ Good!
///     Config: leading: false, trailing: true
///     Input:  1-2-3---4---5-6-|
///     Output: ------3---4-----6|
///
///     final _debounce = Debounce(delay: Duration(seconds: 1), leading: true, trailing: false); ✅ Good!
///     Config: leading: true, trailing: false
///     Input:  1-2-3---4---5-6-|
///     Output: 1-------4---5---|
///
///     final _debounce = Debounce(delay: Duration(seconds: 1), leading: true, trailing: true); ✅ Good!
///     Config: leading: true, trailing: true
///     Input:  1-2-3---4---5-6-|
///     Output: 1-----3-4---5---6|
///
///     final _debounce = Debounce(delay: Duration(seconds: 1), leading: false, trailing: false); ❌ Bad! Output empty!
///     Config: leading: false, trailing: false
///     Input:  1-2-3---4---5-6-|
///     Output: ----------------|

final _debounce = Debounce();

-----------------------------------------------------------------------------------------------------------------------

///     final _throttle = Throttle(interval: const Duration(seconds: 6), leading: true, trailing: false); ✅ Good!
///     Config: leading: true, trailing: false
///     Input:  1-2-3---4-5-6---7-8-|
///     Output: 1-------4-------7---|
///
///     final _throttle = Throttle(interval: const Duration(seconds: 6), leading: false, trailing: true); ✅ Good!
///     Config: leading: false, trailing: true
///     Input:  1-2-3---4-5----6--|
///     Output: ------3-----5-----6|
///
///     final _throttle = Throttle(interval: const Duration(seconds: 6),  leading: true, trailing: true); ✅ Good!
///     Config: leading: true, trailing: true
///     Input:  1-2-----3-----4|
///     Output: 1-----2-----3--|
///
///     final _throttle = Throttle(interval: const Duration(seconds: 6), leading: false, trailing: false); ❌ Bad! Output empty!
///     Config: leading: false, trailing: false
///     Input:  1-2-3---4-5----6--|
///     Output: -------------------|

final _throttle = Throttle();

  • callback: — The function to debounce or throttle.
  • interval, delay: — the interval at which the function can be called
  • leading: — If true, [callback] will be called on the first call before the interval expires.
  • trailing: — If true, [callback] will be called after the interval ends
  • leading && trailing: If both are true, [leading] callback will be called immediately before the interval expires and [trailing] callback will be called after the interval ends (if there were repeated calls)

BackOff #

[BackOff] is used for holding options for retrying a function.

/// With the default configuration functions will be retried up-to 7 times
/// (8 attempts in total), sleeping 1st, 2nd, 3rd, ..., 7th attempt:
///  1. 400 ms ± 25%
///  2. 800 ms ± 25%
///  3. 1600 ms ± 25%
///  4. 3200 ms ± 25%
///  5. 6400 ms ± 25%
///  6. 12800 ms ± 25%
///  7. 25600 ms ± 25%


  final response = await BackOff(
    () => http.get('https://google.com').timeout(
        const Duration(seconds: 10),
        ),
    retryIf: (error, stackTrace, attempt) => error is SocketException || error is TimeoutException,
    initialDelay: const Duration(milliseconds: 200),
    maxAttempts: 8,
    percentageRandomization: 0.25,
    maxDelay: const Duration(seconds: 30),
  ).call();

  • initialDelay: — Defaults to 200 ms, which results in the following delays. Delay factor to double after every attempt.
  • percentageRandomization: — Percentage the delay should be randomized, given as fraction between 0 and 1, (0.0 to 1.0 recommended). If [percentageRandomization] is 0.25 (default) this indicates 25 % of the delay should be increased or decreased by 25 %.
  • maxDelay: — Maximum delay between retries, defaults to 30 seconds.
  • maxAttempts: — Maximum number of attempts before giving up, defaults to 8.
  • retryIf: — Function to determine if a retry should be attempted. If null (default) all errors will be retried.

Contributing #

Contributions are welcome! Please follow these steps:

  1. Fork the repository.
  2. Create a new branch (git checkout -b feature/your-feature).
  3. Commit your changes (git commit -m 'Add your feature').
  4. Push to the branch (git push origin feature/your-feature).
  5. Open a Pull Request.

Contacts #

For issues or suggestions, please open an issue on the GitHub repository.

2
likes
150
points
243
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight Flutter package providing a simple [debounce, throttle, back off] utility to function calls, ideal for optimizing performance in event-driven application.

Repository (GitHub)
View/report issues

Topics

#throttling #debouncing #throttle #debounce #backoff

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on debouncing