bound_mutation
A wrapper around riverpod's Mutation that binds an input parameter to the mutation callback. Instead of capturing input at construction time, you pass it when calling run.
Features
BoundMutation<ResultT, InputR>— mutation with an input parameter passed torunBoundAction<ResultT>— mutation without input- Full
ProviderListenable<MutationState<ResultT>>integration — listen to idle/pending/success/error states - Thin wrapper: delegates directly to
Mutation.runandMutation.reset
Usage
A complete, runnable example lives in example/main.dart
(dart run example/main.dart) — it covers run, error states, reset and
cascade without needing Flutter.
import 'package:bound_mutation/bound_mutation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Define a bound mutation: ResultT = String, InputR = int
final createUser = BoundMutation<String, int>(
(transaction, userId) async {
// transaction gives access to providers
final repo = transaction.get(userRepositoryProvider);
return repo.fetchUserName(userId);
},
);
// Inside a provider or widget with a Ref:
Future<void> loadUser(WidgetRef ref) async {
// Watch mutation state
final state = ref.watch(createUser);
if (state.isIdle) {
final name = await createUser.run(ref, 42);
print('User: $name');
} else if (state.isPending) {
print('Loading...');
} else if (state.isSuccess) {
print('Success: ${(state as MutationSuccess<String>).value}');
} else if (state.hasError) {
final err = state as MutationError<String>;
print('Error: ${err.error}');
}
}
// Reset mutation state (back to idle):
createUser.reset(ref);
With ProviderContainer (e.g. in tests)
final container = ProviderContainer();
final result = await createUser.run(container, 42);
createUser.reset(container);
container.dispose();
BoundAction<ResultT> — mutation without input
final refreshFeed = BoundAction<void>((transaction) async {
final repo = transaction.get(feedRepositoryProvider);
await repo.refresh();
});
await refreshFeed.run(ref);
cascade — reusing a mutation inside another one
Call cascade from within another mutation's callback to reuse its logic in the
same transaction. The cascaded mutation is not run, so its state stays as it is —
only the outer mutation reports pending/success/error:
final refreshEverything = BoundAction<void>((transaction) async {
await refreshFeed.cascade(transaction);
await createUser.cascade(transaction, 42);
});
API
| Method | Description |
|---|---|
BoundMutation(cb, {label}) |
Creates a mutation with a callback (transaction, input) -> Future<ResultT> |
BoundMutation.run(target, input) |
Executes the mutation, returning Future<ResultT> |
BoundMutation.cascade(tsx, input) |
Runs the callback inside an existing transaction, leaving this mutation's state untouched |
BoundAction(cb, {label}) |
Creates a mutation without input, callback (transaction) -> Future<ResultT> |
BoundAction.run(target) |
Executes the mutation, returning Future<ResultT> |
BoundAction.cascade(tsx) |
Runs the callback inside an existing transaction, leaving this mutation's state untouched |
reset(target) |
Resets the mutation state back to MutationIdle |
source |
Returns the underlying Mutation<ResultT> |
== / hashCode |
Delegates to the internal Mutation |
BoundMutation and BoundAction implement ProviderListenable<MutationState<ResultT>>, so they can be watched via ref.watch() or container.listen().