Skip to content
Development
Skill

/flutter

Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing

From plugin
maggy
71073 skills1 agent25 commands1 hook

Claude Code

$ npx -y skills add alinaqi/maggy --skill flutter --agent claude-code

SKILL.md

flutter.SKILL.md
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: () =>
Read more
@alinaqi
Ships withmaggy

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.

71073 skills1 agent
Get the whole plugin

Stars on GitHub

Momentum

stars in total
710
Star history of alinaqi/maggy on GitHub, a chart by star-history.com

Other skills on maggy.

aeo-optimization

aeo-optimization

AI Engine Optimization - semantic triples, page templates, content clusters for AI citations

@alinaqi@alinaqiInstall Skill
agent-teams

agent-teams

Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement

@alinaqi@alinaqiInstall Skill
ai-models

ai-models

Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate

@alinaqi@alinaqiInstall Skill
android-kotlin

android-kotlin

Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

@alinaqi@alinaqiInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about flutter.

Which coding agents can use flutter?
flutter installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.