3.6 KiB
MVVM Pattern
Model-View-ViewModel architectural pattern for Flutter applications.
Overview
MVVM separates application features into three parts:
- Model: Data and business logic (Repositories, Services)
- View: UI presentation (Widgets)
- ViewModel: UI logic and state management
Component Relationships
Every screen or user flow usually contains:
- One View (UI)
- One ViewModel (UI logic)
- One or more Repositories (data sources)
- Zero or more Services (external API access)
- Zero or more Use-cases for complex or reused business logic
Views and ViewModels normally have a one-to-one relationship at the screen or flow level. A View may be composed of many smaller widgets.
View Layer
Views (Widgets)
- Compose widgets to display UI
- Pass events to ViewModel via commands
- Receive data from ViewModel
- Contain minimal logic:
- Simple if-statements for conditional rendering
- Animation logic
- Layout logic based on device info
- Simple routing logic
Important: A View is not a single widget. Views are collections of widgets. One view may contain many widgets. ViewModels have one-to-one relationship with views, not individual widgets.
ViewModels
- Transform repository data into UI state
- Maintain current UI state for rebuilds
- Expose commands (callback functions) for user actions
- Hold state that survives configuration changes
Responsibilities:
- Retrieve application data from repositories
- Filter, sort, aggregate data for presentation
- Track UI state (flags, carousel positions, etc.)
- Expose commands for button presses, form submissions, etc.
- Call repositories directly for simple operations, or call use-cases when the logic is complex, reused, or spans multiple repositories.
Model Layer
Repositories
Single source of truth for model data. Each data type has one repository class.
Responsibilities:
- Poll data from services
- Transform raw data into domain models
- Handle business logic:
- Caching
- Error handling
- Retry logic
- Refreshing data (polling, user actions)
Output: Domain models as Streams or Futures
Relationships:
- Many-to-many with ViewModels
- One ViewModel can use multiple Repositories
- One Repository can be used by multiple ViewModels
- Repositories should never be aware of each other
Use-cases (Optional)
Use-cases sit between ViewModels and Repositories only when they reduce duplication or isolate complex business rules. Do not add them to simple CRUD flows just to fill a layer.
Services
Lowest layer, wrap API endpoints and expose async response objects.
Responsibilities:
- Isolate data-loading
- Stateless (no state held)
- One service per data source
Examples:
- Platform APIs (iOS, Android)
- REST endpoints
- Local files
- Databases
Relationships:
- Many-to-many with Repositories
- One Repository can use multiple Services
- One Service can be used by multiple Repositories
Data Flow
User Interaction Flow
- View: User interaction triggers event
- View: Event handler calls ViewModel command
- ViewModel: Command calls Repository directly, or a Use-case when justified
- Repository: Updates data and returns new data
- ViewModel: Saves new state
- View: UI rebuilds with new state
Data-Originated Flow
- Repository: Polls service for new data
- Repository: Updates data
- ViewModel: Receives new data from Repository
- View: UI rebuilds with new state
Benefits
- Testability: Test ViewModel logic by mocking Repositories
- Maintainability: Clear separation of concerns
- Scalability: Easy to add features without breaking existing code
- Reusability: Components have well-defined interfaces