async_guard 0.0.1
async_guard: ^0.0.1 copied to clipboard
Prevent duplicate async calls, handle loading & errors in one line. Safe lifecycle, double-tap protection, and clean API for Flutter.
async_guard
Prevent duplicate async calls, handle loading & errors in one line.
No more boilerplate. No more bugs. Just guard it.
What is this? #
async_guard is a lightweight utility that wraps any async call with production-ready protections β in a single line:
- π‘οΈ Duplicate prevention β block concurrent calls with the same ID
- β³ Loading state β one callback, automatic start/stop
- π¨ Error handling β built-in try-catch, no boilerplate
- β±οΈ Timeout support β auto-cancel long-running tasks
- 𧬠Lifecycle safe β guards against
setStateafterdispose - π GuardedButton β drop-in widget with double-tap protection & loading indicator
- πͺ Extension syntax β chain
.guard()on anyFuture
Every Flutter developer writes the same 15 lines of loading/error/duplicate boilerplate.
async_guardreplaces all of it with one function call.
Getting Started #
Installation #
dependencies:
async_guard: ^0.0.1
flutter pub get
Minimal Example #
import 'package:async_guard/async_guard.dart';
// That's it. Errors caught, duplicates blocked.
final result = await guard(() => api.login());
Usage #
Basic #
final result = await guard(() => api.login());
If login() throws, the error is caught. If the same call is already running, the duplicate is ignored.
With loading state #
await guard(
() => api.login(),
onLoading: (isLoading) => setState(() => loading = isLoading),
);
onLoading(true) fires before execution, onLoading(false) fires after β regardless of success or failure.
With error handling #
await guard(
() => api.login(),
onError: (e) => showSnackBar('Failed: $e'),
);
Full example #
final user = await guard<User>(
() => api.login(email, password),
id: 'login',
onLoading: (v) => setState(() => isLoading = v),
onSuccess: (user) => navigateTo('/home'),
onError: (e) => showSnackBar('Error: $e'),
timeout: Duration(seconds: 10),
);
GuardedButton #
A drop-in widget that handles everything automatically:
GuardedButton(
onTap: () => api.submit(data),
child: Text('Submit'),
)
- β Disables itself while running
- β Shows a loading indicator
- β Prevents double-tap
- β Zero configuration needed
Customize the loading indicator:
GuardedButton(
onTap: () => api.submit(data),
loadingIndicator: CircularProgressIndicator(color: Colors.white),
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
child: Text('Submit'),
)
Extension Syntax #
Chain .guard() on any Future:
final token = await api.login(email, password).guard(id: 'login');
With callbacks:
await api.login(email, password).guard(
id: 'login',
onLoading: (v) => setState(() => loading = v),
onError: (e) => showError(e),
);
Lifecycle-Safe Async #
Prevent setState after dispose crashes:
@override
void initState() {
super.initState();
safeAsync(this, () async {
final data = await api.fetchData();
setState(() => _data = data);
});
}
Or use the extension on State:
this.runSafe(() async {
final data = await api.fetchData();
setState(() => _data = data);
});
API Reference #
guard<T>() #
| Parameter | Type | Default | Description |
|---|---|---|---|
task |
Future<T> Function() |
β | The async function to execute |
id |
String? |
auto | Unique ID for duplicate prevention |
onLoading |
void Function(bool)? |
β | Loading state callback |
onSuccess |
void Function(T)? |
β | Success callback |
onError |
void Function(Object)? |
β | Error callback |
timeout |
Duration? |
β | Max execution duration |
preventDuplicate |
bool |
true |
Block concurrent duplicate calls |
safeAsync() #
| Parameter | Type | Description |
|---|---|---|
state |
State |
The widget's State object |
task |
Future<void> Function() |
The async function to run |
onError |
void Function(Object)? |
Error callback (lifecycle-safe) |
GuardedButton #
| Parameter | Type | Default | Description |
|---|---|---|---|
onTap |
Future<void> Function() |
β | Async callback on press |
child |
Widget |
β | Button content |
loadingIndicator |
Widget? |
β | Custom loading widget |
disabledWhileLoading |
bool |
true |
Disable button while running |
style |
ButtonStyle? |
β | Optional button style |
Extensions #
| Extension | On | Method | Description |
|---|---|---|---|
GuardExtension<T> |
Future<T> |
.guard() |
Same params as guard<T>() |
SafeAsyncExtension |
State |
.runSafe() |
Shorthand for safeAsync(this, task) |
Why This Package? #
Flutter async code is repetitive and error-prone:
// β What you write today β every single time
bool _isLoading = false;
Future<void> _submit() async {
if (_isLoading) return; // prevent double tap
setState(() => _isLoading = true);
try {
await api.submit();
} catch (e) {
showError(e);
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
// β
With async_guard
await guard(
() => api.submit(),
id: 'submit',
onLoading: (v) => setState(() => _isLoading = v),
onError: (e) => showError(e),
);
15 lines β 5 lines. No bugs, no forgetting mounted, no duplicate calls.
Architecture #
lib/
βββ async_guard.dart β Barrel export
βββ src/
βββ guard.dart β Core guard() function
βββ guard_manager.dart β Singleton task tracker
βββ safe_async.dart β Lifecycle-safe async helpers
βββ guarded_button.dart β Double-tap-proof button widget
βββ extensions.dart β Future<T>.guard() extension
Zero dependencies beyond Flutter SDK. Lightweight, tree-shakeable, and fully tested.
Requirements #
| Requirement | Version |
|---|---|
| Dart SDK | >=3.0.0 <4.0.0 |
| Flutter | >=3.10.0 |
| Null safety | β |
| Dependencies | None (Flutter SDK only) |
π Support #
If this package helps you build better Flutter apps, consider supporting the development:
Your support helps keep this package maintained and up-to-date. Every contribution is greatly appreciated! π
License #
MIT β see LICENSE for details.