flutter-clean-arch

Generate Flutter applications using Clean Architecture with feature-first structure, Riverpod state management, Dio + Retrofit for networking, and fpdart error handling. Use this skill when creating Flutter apps, implementing features with clean architecture patterns, setting up Riverpod providers, handling data with Either type for functional error handling, making HTTP requests with type-safe API clients, or structuring projects with domain/data/presentation layers. Triggers include "Flutter app", "clean architecture", "Riverpod", "feature-first", "state management", "API client", "Retrofit", "Dio", "REST API", or requests to build Flutter features with separation of concerns.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "flutter-clean-arch" with this command: npx skills add duckyman-ai/agent-skills/duckyman-ai-agent-skills-flutter-clean-arch

Flutter Clean Architecture Skill

Generate Flutter applications following Clean Architecture principles with feature-first organization, Riverpod for state management, and functional error handling using fpdart.

Includes Dio + Retrofit for type-safe REST API calls.

Core Principles

Architecture: Clean Architecture (Feature-First)

  • Domain layer: Pure business logic, no dependencies
  • Data layer: Data sources, repositories implementation, data models
  • Presentation layer: UI, state management, view models

Dependency Rule: Presentation → Domain ← Data (Domain has no external dependencies)

State Management: Riverpod 3.0+ with code generation

Note: Riverpod 3.0+ & Freezed 3.0+ Required

Riverpod 3.0+: The XxxRef types (like DioRef, UserRepositoryRef, etc.) have been removed in favor of a unified Ref type.

Riverpod 2.x (Legacy):

@riverpod
SomeType someType(SomeTypeRef ref) { ... }

Riverpod 3.x+ (Current):

@riverpod
SomeType someType(Ref ref) { ... }

Freezed 3.0+: Two major breaking changes from v2:

1. Required sealed / abstract Keyword

All classes using factory constructors now require either sealed or abstract keyword.

Class TypeFreezed 2.x (Legacy)Freezed 3.x+ (Current)
Single constructorclass Personabstract class Person
Union type (multiple constructors)class Resultsealed class Result

Freezed 2.x (Legacy) - Single Constructor:

@freezed
class Person with _$Person {
  const factory Person({
    required String firstName,
    required String lastName,
  }) = _Person;
}

Freezed 3.x+ (Current) - Single Constructor:

@freezed
abstract class Person with _$Person {
  const factory Person({
    required String firstName,
    required String lastName,
  }) = _Person;
}

Freezed 2.x (Legacy) - Union Type:

@freezed
class Result with _$Result {
  const factory Result.success(String data) = Success;
  const factory Result.error(String message) = Error;
}

Freezed 3.x+ (Current) - Union Type:

@freezed
sealed class Result with _$Result {
  const factory Result.success(String data) = Success;
  const factory Result.error(String message) = Error;
}

2. Pattern Matching (.map / .when Removed)

Freezed 3.x no longer generates .map/.when extensions. Use Dart 3's native pattern matching instead.

Freezed 2.x (Legacy) - Using .map:

final model = Model.first('42');
final res = model.map(
  first: (value) => 'first ${value.a}',
  second: (value) => 'second ${value.b} ${value.c}',
);

Freezed 3.x+ (Current) - Using switch expression:

final model = Model.first('42');
final res = switch (model) {
  First(:final a) => 'first $a',
  Second(:final b, :final c) => 'second $b $c',
};

Required versions: This skill requires Riverpod 3.0+ and Freezed 3.0+. Check your version with flutter pub deps | grep riverpod.

Error Handling: fpdart's Either<Failure, T> for functional error handling

Networking: Dio + Retrofit for type-safe REST API calls

Project Structure

lib/
├── core/
│   ├── constants/
│   │   ├── api_constants.dart
│   ├── errors/
│   │   ├── failures.dart
│   │   └── network_exceptions.dart
│   ├── network/
│   │   ├── dio_provider.dart
│   │   └── interceptors/
│   │       ├── auth_interceptor.dart
│   │       ├── logging_interceptor.dart
│   │       └── error_interceptor.dart
│   ├── storage/
│   ├── services/
│   ├── router/
│   │   └── app_router.dart
│   └── utils/
├── shared/ 
├── features/
│   └── [feature_name]/
│       ├── data/
│       │   ├── models/
│       │   │   └── [entity]_model.dart
│       │   ├── datasources/
│       │   │   └── [feature]_api_service.dart
│       │   └── repositories/
│       │       └── [feature]_repository_impl.dart
│       ├── domain/
│       │   ├── entities/
│       │   ├── repositories/
│       │   │   └── [feature]_repository.dart
│       │   └── usecases/
│       │       └── [action]_usecase.dart
│       └── presentation/
│           ├── providers/
│           │   └── [feature]_provider.dart
│           ├── screens/
│           │   └── [feature]_screen.dart
│           └── widgets/
│               └── [feature]_widget.dart
└── main.dart

Quick Start

1. Domain Layer (Entities, Repository Interfaces, UseCases)

// Entity
@freezed
sealed class User with _$User {
  const factory User({
    required String id,
    required String name,
    required String email,
  }) = _User;
}

// Repository Interface
abstract class UserRepository {
  Future<Either<Failure, User>> getUser(String id);
}

// UseCase
class GetUser {
  final UserRepository repository;
  GetUser(this.repository);
  Future<Either<Failure, User>> call(String id) => repository.getUser(id);
}

2. Data Layer (Models, API Service, Repository Implementation)

// Model with JSON serialization
@freezed
sealed class UserModel with _$UserModel {
  const UserModel._();
  const factory UserModel({
    required String id,
    required String name,
    required String email,
  }) = _UserModel;

  factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);

  User toEntity() => User(id: id, name: name, email: email);
}

// Retrofit API Service
@RestApi()
abstract class UserApiService {
  factory UserApiService(Dio dio) = _UserApiService;

  @GET('/users/{id}')
  Future<UserModel> getUser(@Path('id') String id);
}

// Repository Implementation
class UserRepositoryImpl implements UserRepository {
  final UserApiService apiService;

  @override
  Future<Either<Failure, User>> getUser(String id) async {
    try {
      final userModel = await apiService.getUser(id);
      return Right(userModel.toEntity());
    } on DioException catch (e) {
      return Left(Failure.network(NetworkExceptions.fromDioError(e).message));
    }
  }
}

3. Presentation Layer (Providers, Screens)

// Provider
@riverpod
UserApiService userApiService(Ref ref) {
  return UserApiService(ref.watch(dioProvider));
}

@riverpod
UserRepositoryImpl userRepository(Ref ref) {
  return UserRepositoryImpl(ref.watch(userApiServiceProvider));
}

@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  FutureOr<User?> build() => null;

  Future<void> fetchUser(String id) async {
    state = const AsyncLoading();
    final result = await ref.read(userRepositoryProvider).getUser(id);
    state = result.fold(
      (failure) => AsyncError(failure, StackTrace.current),
      (user) => AsyncData(user),
    );
  }
}

// Screen
class UserScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userState = ref.watch(userNotifierProvider);

    return Scaffold(
      body: userState.when(
        data: (user) => Text('Hello ${user?.name}'),
        loading: () => const CircularProgressIndicator(),
        error: (e, _) => Text('Error: $e'),
      ),
    );
  }
}

Code Generation

# Generate all files
dart run build_runner build --delete-conflicting-outputs

# Watch mode
dart run build_runner watch --delete-conflicting-outputs

Best Practices

DO:

  • Keep domain entities pure (no external dependencies)
  • Use freezed with sealed keyword for immutable data classes
  • Handle all error cases with Either<Failure, T>
  • Use riverpod_generator with unified Ref type
  • Separate models (data) from entities (domain)
  • Place business logic in use cases, not in widgets
  • Use Retrofit for type-safe API calls
  • Handle DioException in repositories with NetworkExceptions
  • Use interceptors for cross-cutting concerns (auth, logging)

DON'T:

  • Import Flutter/HTTP libraries in domain layer
  • Mix presentation logic with business logic
  • Use try-catch directly in widgets when using Either
  • Create god objects or god providers
  • Skip the repository pattern
  • Use legacy XxxRef types in new code

Common Issues

IssueSolution
Build runner conflictsdart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
Provider not foundEnsure generated files are imported and run build_runner
Either not unwrappingUse fold(), match(), or getOrElse() to extract values
XxxRef not foundUse unified Ref type instead (Riverpod 3.x+)
sealed keyword errorUpgrade to Dart 3.3+ and Freezed 3.0+
.map / .when not foundFreezed 3.0+ removed these methods. Use Dart 3 switch expression pattern matching instead

Knowledge References

Primary Libraries (used in this skill):

  • Flutter 3.19+: Latest framework features
  • Dart 3.3+: Language features (patterns, records, sealed modifier)
  • Riverpod 3.0+: State management with unified Ref type
  • Dio 5.9+: HTTP client with interceptors
  • Retrofit 4.9+: Type-safe REST API code generation
  • freezed 3.0+: Immutable data classes with code generation
  • json_serializable 6.x: JSON serialization
  • go_router 14.x+: Declarative routing
  • fpdart: Functional error handling with Either type

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

git-convention

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

Planning with files

Implements Manus-style file-based planning to organize and track progress on complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when aske...

Registry SourceRecently Updated
8.4K22Profile unavailable
Coding

Nutrient Document Processing (Universal Agent Skill)

Universal (non-OpenClaw) Nutrient DWS document-processing skill for Agent Skills-compatible products. Best for Claude Code, Codex CLI, Gemini CLI, Cursor, Wi...

Registry SourceRecently Updated
2720Profile unavailable
Coding

vercel-react-best-practices

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

Repository Source
213.8K23Kvercel