---
id: ssh2-flutter-architecting-apps
name: "flutter-architecting-apps"
url: https://skills.yangsir.net/skill/ssh2-flutter-architecting-apps
author: flutter
domain: mobile
tags: ["flutter-architecture", "bloc-pattern", "provider-pattern", "getx", "clean-architecture"]
install_count: 10400
rating: 4.50 (31 reviews)
github: https://github.com/flutter/skills
---

# flutter-architecting-apps

> 讲解如何使用推荐的分层和模式来架构Flutter应用，以提高其可扩展性和可维护性。

**Stats**: 10,400 installs · 4.5/5 (31 reviews)

## Before / After 对比

### Flutter架构：分层清晰，易于维护

## Readme

# Architecting Flutter Applications

## Contents
- [Core Architectural Principles](#core-architectural-principles)
- [Structuring the Layers](#structuring-the-layers)
- [Implementing the Data Layer](#implementing-the-data-layer)
- [Feature Implementation Workflow](#feature-implementation-workflow)
- [Examples](#examples)

## Core Architectural Principles

Design Flutter applications to scale by strictly adhering to the following principles:

*   **Enforce Separation of Concerns:** Decouple UI rendering from business logic and data fetching. Organize the codebase into distinct layers (UI, Logic, Data) and further separate by feature within those layers.
*   **Maintain a Single Source of Truth (SSOT):** Centralize application state and data in the Data layer. Ensure the SSOT is the only component authorized to mutate its respective data.
*   **Implement Unidirectional Data Flow (UDF):** Flow state downwards from the Data layer to the UI layer. Flow events upwards from the UI layer to the Data layer.
*   **Treat UI as a Function of State:** Drive the UI entirely via immutable state objects. Rebuild widgets reactively when the underlying state changes.

## Structuring the Layers

Separate the application into 2 to 3 distinct layers depending on complexity. Restrict communication so that a layer only interacts with the layer directly adjacent to it.

### 1. UI Layer (Presentation)
*   **Views (Widgets):** Build reusable, lean widgets. Strip all business and data-fetching logic from the widget tree. Restrict widget logic to UI-specific concerns (e.g., animations, routing, layout constraints).
*   **ViewModels:** Manage the UI state. Consume domain models from the Data/Logic layers and transform them into presentation-friendly formats. Expose state to the Views and handle user interaction events.

### 2. Logic Layer (Domain) - *Conditional*
*   **If the application requires complex client-side business logic:** Implement a Logic layer containing Use Cases or Interactors. Use this layer to orchestrate interactions between multiple repositories before passing data to the UI layer.
*   **If the application is a standard CRUD app:** Omit this layer. Allow ViewModels to interact directly with Repositories.

### 3. Data Layer (Model)
*   **Responsibilities:** Act as the SSOT for all application data. Handle business data, external API consumption, event processing, and data synchronization.
*   **Components:** Divide the Data layer strictly into **Repositories** and **Services**.

## Implementing the Data Layer

### Services
*   **Role:** Wrap external APIs (HTTP servers, local databases, platform plugins).
*   **Implementation:** Write Services as stateless Dart classes. Do not store application state here.
*   **Mapping:** Create exactly one Service class per external data source.

### Repositories
*   **Role:** Act as the SSOT for domain data. 
*   **Implementation:** Consume raw data from Services. Handle caching, offline synchronization, and retry logic. 
*   **Transformation:** Transform raw API/Service data into clean Domain Models formatted for consumption by ViewModels.

## Feature Implementation Workflow

Follow this sequential workflow when adding a new feature to the application.

**Task Progress:**
- [ ] **Step 1: Define Domain Models.** Create immutable Dart classes representing the core data structures required by the feature.
- [ ] **Step 2: Implement Services.** Create stateless Service classes to handle raw data fetching (e.g., HTTP GET/POST).
- [ ] **Step 3: Implement Repositories.** Create Repository classes that consume the Services, handle caching, and return Domain Models.
- [ ] **Step 4: Implement ViewModels.** Create ViewModels that consume the Repositories. Expose immutable state and define methods (commands) for user actions.
- [ ] **Step 5: Implement Views.** Create Flutter Widgets that bind to the ViewModel state and trigger ViewModel methods on user interaction.
- [ ] **Step 6: Run Validator.** Execute unit tests for Services, Repositories, and ViewModels. Execute widget tests for Views.
    *   *Feedback Loop:* Review test failures -> Fix logic/mocking errors -> Re-run tests until passing.

## Examples

### Data Layer: Service and Repository

```dart
// 1. Service (Stateless API Wrapper)
class UserApiService {
  final HttpClient _client;
  
  UserApiService(this._client);

  Future<Map<String, dynamic>> fetchUserRaw(String userId) async {
    final response = await _client.get('/users/$userId');
    return response.data;
  }
}

// 2. Domain Model (Immutable)
class User {
  final String id;
  final String name;
  
  const User({required this.id, required this.name});
}

// 3. Repository (SSOT & Data Transformer)
class UserRepository {
  final UserApiService _apiService;
  User? _cachedUser;

  UserRepository(this._apiService);

  Future<User> getUser(String userId) async {
    if (_cachedUser != null && _cachedUser!.id == userId) {
      return _cachedUser!;
    }
    
    final rawData = await _apiService.fetchUserRaw(userId);
    final user = User(id: rawData['id'], name: rawData['name']);
    
    _cachedUser = user; // Cache data
    return user;
  }
}
```

### UI Layer: ViewModel and View

```dart
// 4. ViewModel (State Management)
class UserViewModel extends ChangeNotifier {
  final UserRepository _userRepository;
  
  User? user;
  bool isLoading = false;
  String? error;

  UserViewModel(this._userRepository);

  Future<void> loadUser(String userId) async {
    isLoading = true;
    error = null;
    notifyListeners();

    try {
      user = await _userRepository.getUser(userId);
    } catch (e) {
      error = e.toString();
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}

// 5. View (Lean UI)
class UserProfileView extends StatelessWidget {
  final UserViewModel viewModel;

  const UserProfileView({Key? key, required this.viewModel}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, child) {
        if (viewModel.isLoading) return const CircularProgressIndicator();
        if (viewModel.error != null) return Text('Error: ${viewModel.error}');
        if (viewModel.user == null) return const Text('No user data.');
        
        return Text('Hello, ${viewModel.user!.name}');
      },
    );
  }
}
```


---
*Source: https://skills.yangsir.net/skill/ssh2-flutter-architecting-apps*
*Markdown mirror: https://skills.yangsir.net/api/skill/ssh2-flutter-architecting-apps/markdown*