
Flutter Expert
- 12.6k installs
- 10.8k repo stars
- Updated May 20, 2026
- jeffallan/claude-skills
Flutter Expert is a skill for senior-level cross-platform mobile development using Flutter 3+ and Dart.
About
Flutter Expert is a senior mobile engineering skill for building high-performance cross-platform applications with Flutter 3 and Dart. Developers use this skill when implementing state management, navigation, custom widgets, and performance optimization. The skill covers widget patterns, GoRouter navigation, Riverpod/Bloc providers, and Flutter DevTools profiling.
- Cross-platform Flutter 3+ development with Dart: widget creation, state management, and navigation
- Riverpod or Bloc/Cubit state management patterns with const-optimization
- Performance profiling with Flutter DevTools, jank elimination, and rebuild optimization
Flutter Expert by the numbers
- 12,643 all-time installs (skills.sh)
- +140 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #14 of 1,048 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
flutter-expert capabilities & compatibility
- IDEs
- vscode · jetbrains · intellij
What flutter-expert says it does
Senior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart
npx skills add https://github.com/jeffallan/claude-skills --skill flutter-expertAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12.6k |
|---|---|
| repo stars | ★ 10.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | May 20, 2026 |
| Repository | jeffallan/claude-skills ↗ |
How do you implement Bloc state management in Flutter?
Flutter Expert is a senior mobile engineering skill for building high-performance cross-platform applications with Flutter 3 and Dart. Developers use this skill when implementing state management, na
Who is it for?
Mobile developers building cross-platform applications who need guidance on state management and performance
Skip if: Native-only iOS/Android development
When should I use this skill?
A developer writes, refactors, or debugs Flutter code and needs Bloc or Cubit patterns for forms, auth, wizards, or feature modules.
What you get
Bloc and Cubit classes, immutable state models, event handlers, and testable Flutter feature modules with separated UI and business logic.
- Flutter application
- Test suite
- Performance profile
Files
Flutter Expert
Senior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.
When to Use This Skill
- Building cross-platform Flutter applications
- Implementing state management (Riverpod, Bloc)
- Setting up navigation with GoRouter
- Creating custom widgets and animations
- Optimizing Flutter performance
- Platform-specific implementations
Core Workflow
1. Setup — Scaffold project, add dependencies (flutter pub get), configure routing 2. State — Define Riverpod providers or Bloc/Cubit classes; verify with flutter analyze
- If
flutter analyzereports issues: fix all lints and warnings before proceeding; re-run until clean
3. Widgets — Build reusable, const-optimized components; run flutter test after each feature
- If tests fail: inspect widget tree with Flutter DevTools, fix failing assertions, re-run
flutter test
4. Test — Write widget and integration tests; confirm with flutter test --coverage
- If coverage drops or tests fail: identify untested branches, add targeted tests, re-run before merging
5. Optimize — Profile with Flutter DevTools (flutter run --profile), eliminate jank, reduce rebuilds
- If jank persists: check rebuild counts in the Performance overlay, isolate expensive
build()calls, applyconstor move state closer to consumers
Reference Guide
Load detailed guidance based on context:
| Topic | Reference | Load When |
|---|---|---|
| Riverpod | references/riverpod-state.md | State management, providers, notifiers |
| Bloc | references/bloc-state.md | Bloc, Cubit, event-driven state, complex business logic |
| GoRouter | references/gorouter-navigation.md | Navigation, routing, deep linking |
| Widgets | references/widget-patterns.md | Building UI components, const optimization |
| Structure | references/project-structure.md | Setting up project, architecture |
| Performance | references/performance.md | Optimization, profiling, jank fixes |
Code Examples
Riverpod Provider + ConsumerWidget (correct pattern)
// provider definition
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
(ref) => CounterNotifier(),
);
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state = state + 1; // new instance, never mutate
}
// consuming widget — use ConsumerWidget, not StatefulWidget
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}Before / After — State Management
// ❌ WRONG: app-wide state in setState
class _BadCounterState extends State<BadCounter> {
int _count = 0;
void _inc() => setState(() => _count++); // causes full subtree rebuild
}
// ✅ CORRECT: scoped Riverpod consumer
class GoodCounter extends ConsumerWidget {
const GoodCounter({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return IconButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
icon: const Icon(Icons.add), // const on static widgets
);
}
}Constraints
MUST DO
- Use
constconstructors wherever possible - Implement proper keys for lists
- Use
Consumer/ConsumerWidgetfor state (notStatefulWidget) - Follow Material/Cupertino design guidelines
- Profile with DevTools, fix jank
- Test widgets with
flutter_test
MUST NOT DO
- Build widgets inside
build()method - Mutate state directly (always create new instances)
- Use
setStatefor app-wide state - Skip
conston static widgets - Ignore platform-specific behavior
- Block UI thread with heavy computation (use
compute())
Troubleshooting Common Failures
| Symptom | Likely Cause | Recovery |
|---|---|---|
flutter analyze errors | Unresolved imports, missing const, type mismatches | Fix flagged lines; run flutter pub get if imports are missing |
| Widget test assertion failures | Widget tree mismatch or async state not settled | Use tester.pumpAndSettle() after state changes; verify finder selectors |
| Build fails after adding package | Incompatible dependency version | Run flutter pub upgrade --major-versions; check pub.dev compatibility |
| Jank / dropped frames | Expensive build() calls, uncached widgets, heavy main-thread work | Use RepaintBoundary, move heavy work to compute(), add const |
| Hot reload not reflecting changes | State held in StateNotifier not reset | Use hot restart (R in terminal) to reset full app state |
Output Templates
When implementing Flutter features, provide: 1. Widget code with proper const usage 2. Provider/Bloc definitions 3. Route configuration if needed 4. Test file structure
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
sealed class CounterEvent {}
final class CounterIncremented extends CounterEvent {}
final class CounterDecremented extends CounterEvent {}State
class CounterState {
final int value;
const CounterState({required this.value});
CounterState copyWith({int? value}) {
return CounterState(value: value ?? this.value);
}
}Bloc
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)
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}---
Providing Bloc to the Widget Tree
BlocProvider(
create: (_) => CounterBloc(),
child: const CounterScreen(),
);Multiple blocs:
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AuthBloc()),
BlocProvider(create: (_) => ProfileBloc()),
],
child: const AppRoot(),
);---
Using Bloc in Widgets
BlocBuilder (UI rebuilds)
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)
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)
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
context.read<CounterBloc>().add(CounterIncremented());⚠️ Never use `watch` inside callbacks
---
Async Bloc Pattern (API Calls)
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)
redirect: (context, state) {
final authState = context.read<AuthBloc>().state;
if (authState is Unauthenticated) {
return '/login';
}
return null;
}---
Testing Bloc
blocTest<CounterBloc, CounterState>(
'emits incremented value',
build: () => CounterBloc(),
act: (bloc) => bloc.add(CounterIncremented()),
expect: () => [
const CounterState(value: 1),
],
);---
Best Practices (MUST FOLLOW)
✅ Immutable states ✅ Small, focused blocs ✅ One feature = one bloc ✅ Use Cubit when possible ✅ Test all blocs
❌ No UI logic inside blocs ❌ No context usage inside blocs ❌ No mutable state ❌ No massive “god blocs”
---
Quick Reference
| Widget | Purpose |
|---|---|
| BlocBuilder | UI rebuild |
| BlocListener | Side effects |
| BlocConsumer | Both |
| BlocProvider | Dependency injection |
| MultiBlocProvider | Multiple blocs |
GoRouter Navigation
Basic Setup
import 'package:go_router/go_router.dart';
final goRouter = GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = /* check auth */;
if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {
return '/auth/login';
}
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailsScreen(id: id);
},
),
],
),
GoRoute(
path: '/auth/login',
builder: (context, state) => const LoginScreen(),
),
],
);
// In app.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: goRouter,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
);
}
}Navigation Methods
// Navigate and replace history
context.go('/details/123');
// Navigate and add to stack
context.push('/details/123');
// Go back
context.pop();
// Replace current route
context.pushReplacement('/home');
// Navigate with extra data
context.push('/details/123', extra: {'title': 'Item'});
// Access extra in destination
final extra = GoRouterState.of(context).extra as Map<String, dynamic>?;Shell Routes (Persistent UI)
final goRouter = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),
GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),
GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),
],
),
],
);Query Parameters
GoRoute(
path: '/search',
builder: (context, state) {
final query = state.uri.queryParameters['q'] ?? '';
final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;
return SearchScreen(query: query, page: page);
},
),
// Navigate with query params
context.go('/search?q=flutter&page=2');Quick Reference
| Method | Behavior |
|---|---|
context.go() | Navigate, replace stack |
context.push() | Navigate, add to stack |
context.pop() | Go back |
context.pushReplacement() | Replace current |
:param | Path parameter |
?key=value | Query parameter |
Performance Optimization
Profiling Commands
# Run in profile mode
flutter run --profile
# Analyze performance
flutter analyze
# DevTools
flutter pub global activate devtools
flutter pub global run devtoolsCommon Optimizations
Const Widgets
// ❌ Rebuilds every time
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16), // Creates new object
child: Text('Hello'),
);
}
// ✅ Const prevents rebuilds
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: const Text('Hello'),
);
}Selective Provider Watching
// ❌ Rebuilds on any user change
final user = ref.watch(userProvider);
return Text(user.name);
// ✅ Only rebuilds when name changes
final name = ref.watch(userProvider.select((u) => u.name));
return Text(name);RepaintBoundary
// Isolate expensive widgets
RepaintBoundary(
child: ComplexAnimatedWidget(),
)Image Optimization
// Use cached_network_image
CachedNetworkImage(
imageUrl: url,
placeholder: (_, __) => const CircularProgressIndicator(),
errorWidget: (_, __, ___) => const Icon(Icons.error),
)
// Resize images
Image.network(
url,
cacheWidth: 200, // Resize in memory
cacheHeight: 200,
)Compute for Heavy Operations
// ❌ Blocks UI thread
final result = heavyComputation(data);
// ✅ Runs in isolate
final result = await compute(heavyComputation, data);Performance Checklist
| Check | Solution |
|---|---|
| Unnecessary rebuilds | Add const, use select() |
| Large lists | Use ListView.builder |
| Image loading | Use cached_network_image |
| Heavy computation | Use compute() |
| Jank in animations | Use RepaintBoundary |
| Memory leaks | Dispose controllers |
DevTools Metrics
- Frame rendering time: < 16ms for 60fps
- Widget rebuilds: Minimize unnecessary rebuilds
- Memory usage: Watch for leaks
- CPU profiler: Identify bottlenecks
Project Structure
Feature-Based Structure
lib/
├── main.dart
├── app.dart
├── core/
│ ├── constants/
│ │ ├── colors.dart
│ │ └── strings.dart
│ ├── theme/
│ │ ├── app_theme.dart
│ │ └── text_styles.dart
│ ├── utils/
│ │ ├── extensions.dart
│ │ └── validators.dart
│ └── errors/
│ └── failures.dart
├── features/
│ ├── auth/
│ │ ├── data/
│ │ │ ├── repositories/
│ │ │ └── datasources/
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ └── usecases/
│ │ ├── presentation/
│ │ │ ├── screens/
│ │ │ └── widgets/
│ │ └── providers/
│ │ └── auth_provider.dart
│ └── home/
│ ├── data/
│ ├── domain/
│ ├── presentation/
│ └── providers/
├── shared/
│ ├── widgets/
│ │ ├── buttons/
│ │ ├── inputs/
│ │ └── cards/
│ ├── services/
│ │ ├── api_service.dart
│ │ └── storage_service.dart
│ └── models/
│ └── user.dart
└── routes/
└── app_router.dartpubspec.yaml Essentials
dependencies:
flutter:
sdk: flutter
# State Management
flutter_riverpod: ^2.5.0
riverpod_annotation: ^2.3.0
# Navigation
go_router: ^14.0.0
# Networking
dio: ^5.4.0
# Code Generation
freezed_annotation: ^2.4.0
json_annotation: ^4.8.0
# Storage
shared_preferences: ^2.2.0
hive_flutter: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.0
riverpod_generator: ^2.4.0
freezed: ^2.5.0
json_serializable: ^6.8.0
flutter_lints: ^4.0.0Feature Layer Responsibilities
| Layer | Responsibility |
|---|---|
| data/ | API calls, local storage, DTOs |
| domain/ | Business logic, entities, use cases |
| presentation/ | UI screens, widgets |
| providers/ | Riverpod providers for feature |
Main Entry Point
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(const ProviderScope(child: MyApp()));
}
// app.dart
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
routerConfig: router,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: ThemeMode.system,
);
}
}Riverpod State Management
Provider Types
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Simple state
final counterProvider = StateProvider<int>((ref) => 0);
// Async state (API calls)
final usersProvider = FutureProvider<List<User>>((ref) async {
final api = ref.read(apiProvider);
return api.getUsers();
});
// Stream state (real-time)
final messagesProvider = StreamProvider<List<Message>>((ref) {
return ref.read(chatServiceProvider).messagesStream;
});Notifier Pattern (Riverpod 2.0)
@riverpod
class TodoList extends _$TodoList {
@override
List<Todo> build() => [];
void add(Todo todo) {
state = [...state, todo];
}
void toggle(String id) {
state = [
for (final todo in state)
if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,
];
}
void remove(String id) {
state = state.where((t) => t.id != id).toList();
}
}
// Async Notifier
@riverpod
class UserProfile extends _$UserProfile {
@override
Future<User> build() async {
return ref.read(apiProvider).getCurrentUser();
}
Future<void> updateName(String name) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final updated = await ref.read(apiProvider).updateUser(name: name);
return updated;
});
}
}Usage in Widgets
// ConsumerWidget (recommended)
class TodoScreen extends ConsumerWidget {
const TodoScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.completed,
onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
),
);
},
);
}
}
// Selective rebuilds with select
class UserAvatar extends ConsumerWidget {
const UserAvatar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));
return CircleAvatar(
backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,
);
}
}
// Async state handling
class UserProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProfileProvider);
return userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}Quick Reference
| Provider | Use Case |
|---|---|
Provider | Computed/derived values |
StateProvider | Simple mutable state |
FutureProvider | Async operations (one-time) |
StreamProvider | Real-time data streams |
NotifierProvider | Complex state with methods |
AsyncNotifierProvider | Async state with methods |
Widget Patterns
Optimized Widget Pattern
// Use const constructors
class OptimizedCard extends StatelessWidget {
final String title;
final VoidCallback onTap;
const OptimizedCard({
super.key,
required this.title,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
),
),
);
}
}Responsive Layout
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
this.tablet,
required this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1100) return desktop;
if (constraints.maxWidth >= 650) return tablet ?? mobile;
return mobile;
},
);
}
}Custom Hooks (flutter_hooks)
import 'package:flutter_hooks/flutter_hooks.dart';
class CounterWidget extends HookWidget {
@override
Widget build(BuildContext context) {
final counter = useState(0);
final controller = useTextEditingController();
useEffect(() {
// Setup
return () {
// Cleanup
};
}, []);
return Column(
children: [
Text('Count: ${counter.value}'),
ElevatedButton(
onPressed: () => counter.value++,
child: const Text('Increment'),
),
],
);
}
}Sliver Patterns
CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Title'),
background: Image.network(imageUrl, fit: BoxFit.cover),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 100,
),
),
],
)Key Optimization Patterns
| Pattern | Implementation |
|---|---|
| const widgets | Add const to static widgets |
| keys | Use Key for list items |
| select | ref.watch(provider.select(...)) |
| RepaintBoundary | Isolate expensive repaints |
| ListView.builder | Lazy loading for lists |
| const constructors | Always use when possible |
Related skills
How it compares
Pick flutter-expert over generic Flutter skills when the task specifically requires Bloc or Cubit architecture for event-driven mobile features.
FAQ
When should Flutter developers choose Bloc over Riverpod?
flutter-expert recommends Bloc for event-driven workflows, complex business logic, forms, auth flows, and wizards needing explicit event-to-state transitions. Riverpod suits simpler mutable state where Cubit or Bloc overhead is unnecessary.
What Bloc concepts does flutter-expert cover?
flutter-expert covers Event as user input, immutable State objects, Bloc as event-to-state mappers, and Cubit as a state-only variant without explicit events. The skill targets testable separation between Flutter UI and business logic.
Is Flutter Expert safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.