flutter
Streamline Flutter development with standard patterns for state management, routing, and testing.
Install
mkdir -p .claude/skills/flutter && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/3161" && unzip -o skill.zip -d .claude/skills/flutter && rm skill.zipInstalls to .claude/skills/flutter
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Flutter development with Riverpod state management, Freezed, go_router, and mocktail testingKey capabilities
- →Configure Riverpod state management
- →Define Freezed data models
- →Implement go_router navigation
- →Write unit and widget tests
- →Generate code with build_runner
How it works
Uses an opinionated project structure and specific libraries to manage state, navigation, and data models in Flutter applications.
Inputs & outputs
When to use flutter
- →Create a new feature with Riverpod state management
- →Generate code for Freezed models
- →Write widget tests with mocktail
- →Configure application routing
About this skill
Flutter Skill
Project Structure
project/
├── lib/
│ ├── core/ # Core utilities
│ │ ├── constants/ # App constants
│ │ ├── extensions/ # Dart extensions
│ │ ├── router/ # go_router configuration
│ │ │ └── app_router.dart
│ │ └── theme/ # App theme
│ │ └── app_theme.dart
│ ├── data/ # Data layer
│ │ ├── models/ # Freezed data models
│ │ ├── repositories/ # Repository implementations
│ │ └── services/ # API services
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Business entities
│ │ └── repositories/ # Repository interfaces
│ ├── presentation/ # UI layer
│ │ ├── common/ # Shared widgets
│ │ ├── features/ # Feature modules
│ │ │ └── feature_name/
│ │ │ ├── providers/ # Riverpod providers
│ │ │ ├── widgets/ # Feature-specific widgets
│ │ │ └── feature_screen.dart
│ │ └── providers/ # Global providers
│ ├── main.dart
│ └── app.dart
├── test/
│ ├── unit/ # Unit tests
│ ├── widget/ # Widget tests
│ └── integration/ # Integration tests
├── pubspec.yaml
├── analysis_options.yaml
└── CLAUDE.md
Riverpod State Management
Provider Types
// Simple value provider
final appNameProvider = Provider<String>((ref) => 'My App');
// StateProvider for simple mutable state
final counterProvider = StateProvider<int>((ref) => 0);
// NotifierProvider for complex state logic
final userProvider = NotifierProvider<UserNotifier, User?>(() => UserNotifier());
// AsyncNotifierProvider for async operations
final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(
() => UsersNotifier(),
);
// FutureProvider for simple async data
final configProvider = FutureProvider<Config>((ref) async {
return await ref.watch(configServiceProvider).loadConfig();
});
// StreamProvider for real-time data
final messagesProvider = StreamProvider<List<Message>>((ref) {
return ref.watch(messageServiceProvider).watchMessages();
});
// Family providers for parameterized data
final userByIdProvider = FutureProvider.family<User, String>((ref, userId) async {
return await ref.watch(userRepositoryProvider).getUser(userId);
});
Notifier Pattern
@riverpod
class Users extends _$Users {
@override
Future<List<User>> build() async {
return await _fetchUsers();
}
Future<List<User>> _fetchUsers() async {
final repository = ref.read(userRepositoryProvider);
return await repository.getUsers();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => _fetchUsers());
}
Future<void> addUser(User user) async {
final repository = ref.read(userRepositoryProvider);
await repository.addUser(user);
ref.invalidateSelf();
}
}
AsyncValue Handling
class UsersScreen extends ConsumerWidget {
const UsersScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return usersAsync.when(
data: (users) => UsersList(users: users),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorDisplay(
error: error,
onRetry: () => ref.invalidate(usersProvider),
),
);
}
}
// Pattern matching alternative
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return switch (usersAsync) {
AsyncData(:final value) => UsersList(users: value),
AsyncLoading() => const LoadingIndicator(),
AsyncError(:final error) => ErrorDisplay(error: error),
};
}
ref Methods
// watch - rebuilds when provider changes
final users = ref.watch(usersProvider);
// read - one-time read, no rebuild
void onButtonPressed() {
ref.read(counterProvider.notifier).state++;
}
// listen - react to changes without rebuild
ref.listen(authProvider, (previous, next) {
if (next == null) {
context.go('/login');
}
});
// invalidate - force refresh
ref.invalidate(usersProvider);
// keepAlive - prevent auto-dispose
final link = ref.keepAlive();
// Later: link.close() to allow disposal
Freezed Data Models
Model Definition
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
@Default(false) bool isActive,
DateTime? createdAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Union types for states
@freezed
sealed class AuthState with _$AuthState {
const factory AuthState.initial() = _Initial;
const factory AuthState.loading() = _Loading;
const factory AuthState.authenticated(User user) = _Authenticated;
const factory AuthState.unauthenticated() = _Unauthenticated;
const factory AuthState.error(String message) = _Error;
}
Using Freezed Unions
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
return authState.when(
initial: () => const SplashScreen(),
loading: () => const LoadingScreen(),
authenticated: (user) => HomeScreen(user: user),
unauthenticated: () => const LoginScreen(),
error: (message) => ErrorScreen(message: message),
);
}
go_router Navigation
Router Configuration
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authProvider);
return GoRouter(
initialLocation: '/',
refreshListenable: authState,
redirect: (context, state) {
final isLoggedIn = authState.valueOrNull != null;
final isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/';
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'user/:id',
builder: (context, state) => UserScreen(
userId: state.pathParameters['id']!,
),
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
});
Navigation
// Navigate to route
context.go('/user/123');
// Push onto stack
context.push('/user/123');
// Pop current route
context.pop();
// Replace current route
context.pushReplacement('/home');
// Named routes
context.goNamed('user', pathParameters: {'id': '123'});
Widget Patterns
ConsumerWidget vs ConsumerStatefulWidget
// Stateless with Riverpod
class UserCard extends ConsumerWidget {
const UserCard({super.key, required this.userId});
final String userId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userByIdProvider(userId));
return user.when(
data: (user) => Card(child: Text(user.name)),
loading: () => const CardSkeleton(),
error: (e, _) => ErrorCard(error: e),
);
}
}
// Stateful with Riverpod
class SearchScreen extends ConsumerStatefulWidget {
const SearchScreen({super.key});
@override
ConsumerState<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends ConsumerState<SearchScreen> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final results = ref.watch(searchProvider(_controller.text));
return Column(
children: [
TextField(
controller: _controller,
onChanged: (_) => setState(() {}),
),
Expanded(child: SearchResults(results: results)),
],
);
}
}
HookConsumerWidget (with flutter_hooks)
class AnimatedCounter extends HookConsumerWidget {
const AnimatedCounter({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = useAnimationController(duration: const Duration(milliseconds: 300));
final count = ref.watch(counterProvider);
useEffect(() {
controller.forward(from: 0);
return null;
}, [count]);
return ScaleTransition(
scale: controller,
child: Text('$count'),
);
}
}
Testing with Mocktail
Unit Tests
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpod/riverpod.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late MockUserRepository mockRepository;
late ProviderContainer container;
setUp(() {
mockRepository = MockUserRepository();
container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
});
tearDown(() {
container.dispose();
});
test('usersProvider returns list of users', () async {
final users = [User(id: '1', name: 'John', email: '[email protected]')];
when(() => mockRepository.getUsers()).thenAnswer((_) async => users);
final result = await container.read(usersProvider.future);
expect(result, equals(users));
verify(() => mockRepository.getUsers()).called(1);
});
}
Widget Tests
void main() {
testWidgets('UserCard displays user name',
---
*Content truncated.*
When not to use it
- →Projects not using Riverpod or Freezed
- →Simple apps where boilerplate outweighs benefits
Prerequisites
Limitations
- →Requires build_runner for code generation
- →Strict adherence to Riverpod patterns
How it compares
Provides a complete, pre-configured architectural pattern instead of ad-hoc implementation.
Compared to similar skills
flutter side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| flutter (this skill) | 13 | 4mo | No flags | Intermediate |
| flutter-expert | 73 | 4mo | No flags | Advanced |
| frontend-mobile-development-component-scaffold | 1 | 4mo | No flags | Intermediate |
| flutter-bloc-state-management | 0 | 3mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by alinaqi
View all by alinaqi →You might also like
flutter-expert
sickn33
Master Flutter development with Dart 3, advanced widgets, and multi-platform deployment. Handles state management, animations, testing, and performance optimization for mobile, web, desktop, and embedded platforms. Use PROACTIVELY for Flutter architecture, UI implementation, or cross-platform features.
frontend-mobile-development-component-scaffold
sickn33
You are a React component architecture expert specializing in scaffolding production-ready, accessible, and performant components. Generate complete component implementations with TypeScript, tests, s
flutter-bloc-state-management
HoangNguyen0403
Implement BLoC/Cubit state management correctly in Flutter. Use when writing, modifying, reviewing, or testing any BLoC, Cubit, state, or event file.
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
ui-ux-expert-skill
fercracix33
Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.
playwright-browser-automation
lackeyjb
Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.