/flutter
Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing
$ npx -y skills add alinaqi/maggy --skill flutter --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/flutter
Context preview
The summary Claude sees to decide when to auto-load this skill.
Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing
SKILL.md
flutter.SKILL.mdname: flutter
description: Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing
when-to-use: When working on Flutter/Dart code
user-invocable: false
paths: ["**/*.dart", "pubspec.yaml", "lib/**", "test/**"]
effort: medium
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: () =>Read more
name: flutter description: Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing when-to-use: When working on Flutter/Dart code user-invocable: false paths: ["**/*.dart", "pubspec.yaml", "lib/**", "test/**"] effort: medium
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: () =>Turn Claude Code into a self-reviewing, test-enforced engineering system that remembers context across sessions — then route work across 13 models from a single dashboard.
Repo: alinaqi/maggy
Other skills on maggy.
- /aeo-optimization
AI Engine Optimization - semantic triples, page templates, content clusters for AI citations
Open skill - /agent-teams
Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement
Open skill - /agentic-development
Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)
Open skill - /ai-models
Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate
Open skill - /android-java
Android Java development with MVVM, ViewBinding, and Espresso testing
Open skill - /android-kotlin
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
Open skill

