Skip to Content

MutationCubit

The MutationCubit is an abstract base class for performing server-side mutations, emitting MutationState changes. Extend it to create cubits that handle operations that modify data on the server.

Version 0.3.0+ adds powerful lifecycle hooks for side effects and optimistic updates.

Basic Usage

Extend MutationCubit and implement the required getter:

import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fasq_bloc/fasq_bloc.dart'; class CreateUserMutationCubit extends MutationCubit<User, String> { @override Future<User> Function(String variables) get mutationFn => (name) => api.createUser(name); }

Triggering Mutations

Call .mutate() with variables. You can optionally provide lifecycle callbacks directly here.

context.read<CreateUserMutationCubit>().mutate( 'John Doe', onSuccess: (user) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Welcome, ${user.name}!')), ); }, onError: (error, context) { print('Failed: $error'); } );

Lifecycle Hooks & Side Effects

Mutations often require side effects (logging, navigation, invalidation). You can define these in two places:

  1. In options (Global defaults for this Cubit)
  2. In .mutate() (Specific to the call site)

1. In options

Use this for things that always happen, like cache invalidation.

class CreateUserMutationCubit extends MutationCubit<User, String> { // ... mutationFn ... @override MutationOptions<User, String>? get options => MutationOptions( onSuccess: (user) { // Always invalidate the list when a user is created QueryClient().invalidateQueries('users'); }, ); }

2. In .mutate()

Use this for UI-specific actions like Navigation or SnackBars.

cubit.mutate( 'Jane', onSuccess: (user) => Navigator.of(context).pop(), );

Optimistic Updates

Update the UI before the server responds.

cubit.mutate( 'New Task', onMutate: () async { // 1. Cancel background refetches to avoid overwriting our optimistic update await QueryClient().cancelQueries('todos'); // 2. Snapshot the previous value final previousTodos = QueryClient().getQueryData<List<Todo>>('todos'); // 3. Optimistically update the cache QueryClient().setQueryData<List<Todo>>( 'todos', [...?previousTodos, Todo(id: 'temp', title: 'New Task')] ); // 4. Return context for potential rollback return { 'previousTodos': previousTodos }; }, onError: (error, context) { // 5. Rollback on error if (context != null) { QueryClient().setQueryData('todos', context['previousTodos']); } }, onSettled: () { // 6. Always refetch to ensure we are in sync with server QueryClient().invalidateQueries('todos'); } );

Status Handling

Handle different mutation statuses:

BlocBuilder<CreateUserMutationCubit, MutationState<User>>( builder: (context, state) { if (state.isLoading) return CircularProgressIndicator(); if (state.hasError) return Text('Error: ${state.error}'); return ElevatedButton( onPressed: () => context.read<CreateUserMutationCubit>().mutate('John'), child: Text('Create'), ); }, )

Resetting State

Reset mutation state to idle to clear errors or data (e.g., when closing a dialog).

context.read<CreateUserMutationCubit>().reset();

Next Steps

Last updated on