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 to run
  • BoundAction<ResultT> — mutation without input
  • Full ProviderListenable<MutationState<ResultT>> integration — listen to idle/pending/success/error states
  • Thin wrapper: delegates directly to Mutation.run and Mutation.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().

Libraries

bound_mutation