Flutter DDD Patterns
Architecture Overview
lib/apps/
├── domain/{domain}/
│ ├── models/ # {entity}_model.dart (Freezed 3.x)
│ ├── services/ # {domain}_service.dart (Riverpod 3.x AsyncNotifier)
│ ├── pages/{page}/
│ │ ├── {page}_page.dart
│ │ └── providers/ # {feature}_provider.dart
│ └── components/
├── infra/ # Dio client, interceptors, exception
├── application/ # Cross-feature (storage, auth)
├── ui/ # theme, router, common components
├── generated/api/ # swagger_parser output (수동 편집 금지)
└── global/ # constants, utils, hooks, types
Core Rules:
- No Repository/DataSource — Provider → Dio 직접 호출
- No DTO/Entity 분리 — 모든 모델은
_model.dart+Modelsuffix - Freezed 3.x
@freezed abstract class+const factory - Riverpod 3.x
@riverpod+Ref(notFooRef) - Always
package:app/...imports (no relative)
Quick Reference Checklists
Freezed Model
-
@freezedannotation -
abstract class {Entity}Model with _${Entity}Model -
const {Entity}Model._();(custom methods가 있을 때) -
const factoryconstructor (const 필수) -
fromJsonfactory - Part files:
.freezed.dart+.g.dart - 파일명
{entity}_model.dart, 절대_dto.dart아님
Riverpod Provider/Service
-
@riverpodannotation (lowercase) - Class:
extends _${Name}| Function:Ref refparameter -
build()안에서만ref.watch— helper/action 메서드에서는ref.read -
AsyncValue.guardfor error handling - UI 상태 반응은 Page의
build()에서ref.listen사용
Router (go_router)
- 정적 경로(
/posts/new)를 동적 경로(/posts/:id) 앞에 등록 - Route class:
static const path,static const name,go(),push() - RouterClient:
abstract final classwith const instances
Auth State Management
-
authProvider로그인 실패 시AsyncData(unauthenticated())복귀 (절대AsyncError아님) - 에러 전파:
Error.throwWithStackTrace(e, st)(rethrow대신 — 상태 설정 후 전파) -
emailLoginProvider가 에러 메시지 담당 (역할 분리) - Login UI:
ref.listen(authProvider)→ 성공 네비게이션,ref.listen(emailLoginProvider)→ 에러 표시 - 에러 메시지:
AppException→ExceptionHandler.getUserMessage(), 그 외 → generic 메시지 - Router redirect:
authValue == null→ 미인증 취급 (defense-in-depth)
Auth Interceptor
- Retry Dio에
ResponseInterceptor+ErrorInterceptor포함 -
AuthInterceptor는 retry Dio에서 제외 (무한루프 방지) - 로그인 시
refreshToken저장 확인
Pagination
-
PaginatedResponse<T>model inlib/global/types/paginated_response.dart -
PaginationMetawith offset/pageSize/totalItemCount/isFirst/isLast - Provider uses
_pagecounter +_hasMoreflag -
loadMore()appends to existing list - UI uses
NotificationListener<ScrollNotification>orScrollController
AsyncValueWidget
- Use
AsyncValueWidgetinstead of.when(data:, error:, loading:)inline - Located at
lib/apps/ui/common/async_value_widget.dart - Supports
emptyCheckandemptyMessagefor empty states - Custom
loadinganderrorbuilders optional
Form Validation
- Use
Validatorsfromlib/global/utils/validators.dart -
Validators.compose([...])for multiple rules - Available:
required,email,minLength,maxLength,phone,password
Loading Overlay
-
withLoaderOverlay(context, () async { ... })for button actions - Requires
LoaderOverlaywidget in tree (wrap Scaffold or MaterialApp) - Auto-hides on success or error
Theme (Dual Mode)
-
AppTheme.fromTokens()— Figma token-based (AppColors → ColorScheme) -
AppTheme.fromSeed(seedColor: Colors.blue)— Material 3 auto-generated - Both modes:
Theme.of(context).colorScheme.primaryworks identically -
AppSpacing,AppRadiususable in both modes
Naming Conventions
| Type | File | Class |
|---|---|---|
| Model | {entity}_model.dart |
{Entity}Model |
| Service | {domain}_service.dart |
{Domain}Service |
| Provider | {feature}_provider.dart |
{Feature}Provider |
| Page | {page}_page.dart |
{Page}Page |
| Route | domains/{domain}.dart |
{Page}Route |
Import Order
import 'dart:async'; // 1. Dart
import 'package:freezed_annotation/freezed_annotation.dart'; // 2. Package
import 'package:app/apps/domain/.../model.dart'; // 3. Project (absolute)
part 'file.g.dart'; // 4. Part
Code Generation
dart run swagger_parser # API clients
dart run build_runner build --delete-conflicting-outputs # Freezed + Riverpod
Detailed References
- Freezed 3.x Guide — Union types, nested models, enums, custom JSON, testing
- Riverpod 3.x Guide — AsyncNotifier, provider types, family, ref.watch vs ref.read
- API Client Patterns — CRUD, pagination, file upload, error handling
- Anti-Patterns — 10가지 금지 패턴 + Auth Interceptor 올바른 패턴
Working Examples
- user_model_example.dart — Freezed model with custom methods
- auth_service_example.dart — Riverpod AsyncNotifier service
- post_create_page_example.dart — ref.listen UI 상태 처리
- paginated_list_example.dart — Pagination Provider + infinite scroll UI
- form_with_validation_example.dart — Validators + withLoaderOverlay