skills/flutter-architecture/references/layers.md

201 lines
6.2 KiB
Markdown

# Architecture Layers
Detailed guide to layer responsibilities and interactions in Flutter apps.
## Layer Overview
```
┌─────────────────────────────────────┐
│ UI Layer │
│ ┌────────────┐ ┌─────────────┐ │
│ │ Views │ │ ViewModels │ │
│ └────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Domain Layer (Optional) │
│ ┌─────────────────────┐ │
│ │ Use-cases │ │
│ └─────────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Data Layer │
│ ┌────────────┐ ┌─────────────┐ │
│ │Repositories │ │ Services │ │
│ └────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
```
## UI Layer
Responsibility: Interact with user, display data, receive input.
### Views
**What they do:**
- Compose widgets to present data
- Handle user interactions (taps, form inputs)
- Pass events to ViewModel
- Re-render when ViewModel state changes
**What they don't do:**
- Business logic
- Data transformation
- State persistence
- API calls
**Logic allowed in Views:**
- Simple if-statements for conditional rendering
- Animation logic
- Layout logic (responsive design, orientation)
- Simple routing
### ViewModels
**What they do:**
- Transform repository data into UI state
- Maintain UI state (survives configuration changes)
- Expose commands for user actions
- Aggregate data from multiple repositories
**What they don't do:**
- Direct UI manipulation
- Platform API calls
- File I/O
- Network requests directly (use repositories)
**State management:**
- Use ChangeNotifier for simple apps
- Consider Provider, Riverpod, or Bloc for complex apps
- Expose state as Streams or ChangeNotifiers
- Call Repositories directly for simple flows; introduce Use-cases only when
business logic is complex, reused, or spans multiple Repositories.
## Domain Layer (Optional)
**Purpose**: Abstract complex business logic from ViewModels.
### When to add Domain Layer
Add when ViewModels have logic that:
1. Merges data from multiple repositories
2. Is exceedingly complex
3. Will be reused by different ViewModels
### Use-cases
**What they do:**
- Take data from repositories
- Transform for UI layer consumption
- Encapsulate reusable business logic
**Relationships:**
- Use-cases depend on Repositories
- Use-cases and Repositories: many-to-many
- ViewModels may depend on Use-cases for complex operations and on Repositories
directly for simple operations.
**Pros:**
- Avoid code duplication in ViewModels
- Improve testability by separating complex logic
- Improve code readability in ViewModels
**Cons:**
- Increases complexity
- Additional mocks for testing
- Adds boilerplate code
**Recommendation**: Add use-cases only when needed. Don't force all data access through use-cases for simple operations.
## Data Layer
Responsibility: Handle business data and logic.
### Repositories
**What they do:**
- Single source of truth for data types
- Poll data from Services
- Transform raw data into domain models
- Handle business logic:
- Caching
- Error handling
- Retry logic
- Data refresh (polling, user-triggered)
**What they don't do:**
- Direct UI rendering
- Business logic better suited for Domain layer
- Direct knowledge of other repositories
**Output:**
- Domain models (data classes tailored for app needs)
- Exposed as Streams (real-time) or Futures (one-time)
**Relationships:**
- Many-to-many with ViewModels
- Many-to-many with Services
- Should not be aware of each other
### Services
**What they do:**
- Wrap external data sources
- Expose async response objects (Future, Stream)
- Isolate data loading
- One service per data source
**What they don't do:**
- Hold state (stateless)
- Business logic
- Data transformation
- Caching
**Examples:**
- Platform APIs (iOS/Android native)
- REST/GraphQL API clients
- Local file access
- Database access
- Platform plugins (geolocation, camera, etc.)
**Relationships:**
- Many-to-many with Repositories
- Can be shared across the app
## Layer Communication Rules
1. **Lower layers stay independent**: Data doesn't depend on Domain or UI;
Domain doesn't depend on UI.
2. **Unidirectional data flow**: Data -> ViewModel -> View.
3. **Events flow opposite**: View -> ViewModel -> Repository -> Service, or
View -> ViewModel -> Use-case -> Repository -> Service when a Use-case is
justified.
4. **No direct service calls from UI**: Views and ViewModels don't call
Services directly.
5. **No forced Domain layer**: Skipping Use-cases is valid for simple logic.
## When to Use Which Layer
**Small CRUD apps:**
- UI Layer (Views + ViewModels)
- Data Layer (Repositories + Services)
- Skip Domain Layer
**Apps with complex business logic:**
- UI Layer
- Domain Layer (for complex, reusable logic)
- Data Layer
- ViewModels may use Repositories directly for simple operations, Use-cases for complex
**Enterprise apps with complex rules:**
- All three layers
- Consider enforcing Use-cases for all data access
## Testing Strategy
**UI Layer Tests:**
- Widget tests for Views
- Unit tests for ViewModels (mock Repositories)
**Domain Layer Tests:**
- Unit tests for Use-cases (mock Repositories)
**Data Layer Tests:**
- Unit tests for Repositories (mock Services)
- Integration tests for Services (actual APIs/databases)