
Flutter Expert
Implement predictable Flutter feature state with Bloc/Cubit patterns when forms, auth, or multi-step flows need explicit events and testable transitions.
Overview
Flutter Expert is an agent skill for the Build phase that guides Bloc and Cubit state management patterns for event-driven Flutter features.
Install
npx skills add https://github.com/jeffallan/claude-skills --skill flutter-expertWhat is this skill?
- Decision table: Riverpod for simple mutable state; Bloc for event-driven workflows, forms, auth, wizards
- Core model: Event → State via Bloc; Cubit variant without events
- Sealed event classes and immutable `CounterState` with `copyWith` examples
- `flutter_bloc` `Bloc`/`on<Event>` handler patterns
- Separation of UI from business logic for testable flows
- Documented Bloc vs Riverpod decision table with four recommended use-case rows
- Four core concepts table: Event, State, Bloc, Cubit
Adoption & trust: 11.6k installs on skills.sh; 9.7k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Flutter feature logic is tangled in widgets and you cannot test auth, forms, or wizards as clear event-to-state flows.
Who is it for?
Solo Flutter developers shipping apps with forms, auth, or wizards who want Bloc conventions instead of ad-hoc `setState`.
Skip if: Tiny screens with only local mutable counters or toggles where the skill itself recommends Riverpod or simpler state.
When should I use this skill?
You are building Flutter features that need event-driven, testable state (forms, auth, wizards) and want Bloc/Cubit structure.
What do I get? / Deliverables
You get Bloc/Cubit scaffolding—sealed events, immutable states, and `flutter_bloc` handlers—so UI stays thin and business rules are unit-testable.
- Event and state class definitions with sealed/copyWith patterns
- Bloc/Cubit classes with `on<Event>` handlers
- Guidance on when to choose Cubit vs full Bloc
Recommended Skills
Journey fit
Bloc setup is product construction work on the client, so the canonical shelf is Build → frontend for Flutter UI modules. The skill encodes UI-facing state management (events, immutable state, Bloc widgets), not backend APIs or store listing work.
How it compares
Opinionated Bloc/Cubit template skill—not a full Flutter UI kit or a backend/API integration skill.
Common Questions / FAQ
Who is flutter-expert for?
Indie and solo Flutter developers using Claude or similar agents to implement maintainable state management for non-trivial mobile features.
When should I use flutter-expert?
During Build frontend work when adding or refactoring Bloc-based modules for auth, forms, or multi-step flows in a Flutter app.
Is flutter-expert safe to install?
It is documentation and code-pattern guidance only with no special system access. Review the Security Audits panel on this Prism page like any community skill before relying on it in production repos.
SKILL.md
READMESKILL.md - Flutter Expert
# Bloc State Management ## When to Use Bloc Use **Bloc/Cubit** when you need: * Explicit event → state transitions * Complex business logic * Predictable, testable flows * Clear separation between UI and logic | Use Case | Recommended | | ---------------------- | ----------- | | Simple mutable state | Riverpod | | Event-driven workflows | Bloc | | Forms, auth, wizards | Bloc | | Feature modules | Bloc | --- ## Core Concepts | Concept | Description | | ------- | ---------------------- | | Event | User/system input | | State | Immutable UI state | | Bloc | Event → State mapper | | Cubit | State-only (no events) | --- ## Basic Bloc Setup ### Event ```dart sealed class CounterEvent {} final class CounterIncremented extends CounterEvent {} final class CounterDecremented extends CounterEvent {} ``` ### State ```dart class CounterState { final int value; const CounterState({required this.value}); CounterState copyWith({int? value}) { return CounterState(value: value ?? this.value); } } ``` ### Bloc ```dart import 'package:flutter_bloc/flutter_bloc.dart'; class CounterBloc extends Bloc<CounterEvent, CounterState> { CounterBloc() : super(const CounterState(value: 0)) { on<CounterIncremented>((event, emit) { emit(state.copyWith(value: state.value + 1)); }); on<CounterDecremented>((event, emit) { emit(state.copyWith(value: state.value - 1)); }); } } ``` --- ## Cubit (Recommended for Simpler Logic) ```dart class CounterCubit extends Cubit<int> { CounterCubit() : super(0); void increment() => emit(state + 1); void decrement() => emit(state - 1); } ``` --- ## Providing Bloc to the Widget Tree ```dart BlocProvider( create: (_) => CounterBloc(), child: const CounterScreen(), ); ``` Multiple blocs: ```dart MultiBlocProvider( providers: [ BlocProvider(create: (_) => AuthBloc()), BlocProvider(create: (_) => ProfileBloc()), ], child: const AppRoot(), ); ``` --- ## Using Bloc in Widgets ### BlocBuilder (UI rebuilds) ```dart class CounterScreen extends StatelessWidget { const CounterScreen({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<CounterBloc, CounterState>( buildWhen: (prev, curr) => prev.value != curr.value, builder: (context, state) { return Text( state.value.toString(), style: Theme.of(context).textTheme.displayLarge, ); }, ); } } ``` --- ### BlocListener (Side Effects) ```dart BlocListener<AuthBloc, AuthState>( listenWhen: (prev, curr) => curr is AuthFailure, listener: (context, state) { if (state is AuthFailure) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(state.message))); } }, child: const LoginForm(), ); ``` --- ### BlocConsumer (Builder + Listener) ```dart BlocConsumer<FormBloc, FormState>( listener: (context, state) { if (state.status == FormStatus.success) { context.pop(); } }, builder: (context, state) { return ElevatedButton( onPressed: state.isValid ? () => context.read<FormBloc>().add(FormSubmitted()) : null, child: const Text('Submit'), ); }, ); ``` --- ## Accessing Bloc Without Rebuilds ```dart context.read<CounterBloc>().add(CounterIncremented()); ``` ⚠️ **Never use `watch` inside callbacks** --- ## Async Bloc Pattern (API Calls) ```dart on<UserRequested>((event, emit) async { emit(const UserState.loading()); try { final user = await repository.fetchUser(); emit(UserState.success(user)); } catch (e) { emit(UserState.failure(e.toString())); } }); ``` --- ## Bloc + GoRouter (Auth Guard Example) ```dart redirect: (context, state) { final authState = context.read<AuthBloc>().state; if (authState is Unauthenticated) { return '/login'; } return null; } ``` --- ## Testing Bloc ```dar