架构设计模式:MVVM架构应用

架构设计模式:MVVM架构应用

在Flutter应用开发中,选择合适的架构模式对于构建可维护、可测试和可扩展的应用至关重要。MVVM(Model-View-ViewModel)作为一种流行的架构模式,在Flutter开发中得到了广泛应用。本文将深入探讨MVVM架构在Flutter中的实现方式、最佳实践以及实际应用案例。

一、MVVM架构基础

1.1 什么是MVVM架构

MVVM是Model-View-ViewModel的缩写,它是一种将应用程序的逻辑和UI分离的架构模式。MVVM架构由三个核心组件组成:

  • Model(模型):代表应用程序的数据和业务逻辑,负责数据的获取、存储和处理。
  • View(视图):负责UI的展示,在Flutter中通常是Widget树。
  • ViewModel(视图模型):作为View和Model之间的桥梁,负责处理View的业务逻辑,并将Model的数据转换为View可以直接使用的形式。

1.2 MVVM架构的核心原则

  1. 关注点分离:UI逻辑与业务逻辑分离,使代码更加清晰和易于维护。
  2. 数据绑定:View和ViewModel之间通过数据绑定机制实现自动同步。
  3. 依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖于抽象。
  4. 可测试性:由于业务逻辑与UI分离,可以更容易地进行单元测试。

1.3 MVVM与其他架构模式的比较

架构模式 优点 缺点
MVC 简单易懂,适合小型应用 Controller容易变得臃肿,测试困难
MVP 提高了可测试性,View和Presenter解耦 需要为每个View创建Presenter,代码量增加
MVVM 数据绑定减少样板代码,高可测试性 学习曲线较陡,小型应用可能过度设计
Clean Architecture 高度解耦,适合大型应用 结构复杂,初始开发成本高

二、Flutter中实现MVVM架构

2.1 Flutter中MVVM的实现方式

Flutter中实现MVVM架构通常需要结合状态管理工具。以下是几种常见的实现方式:

2.1.1 使用Provider实现MVVM

Provider是Flutter官方推荐的状态管理解决方案,它基于InheritedWidget,提供了一种简单的方式来实现依赖注入和状态管理。

dart 复制代码
// 定义Model
class User {
  final String id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

// 定义ViewModel
class UserViewModel extends ChangeNotifier {
  User? _user;
  bool _isLoading = false;
  String? _error;
  
  User? get user => _user;
  bool get isLoading => _isLoading;
  String? get error => _error;
  
  Future<void> fetchUser(String userId) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      // 模拟网络请求
      await Future.delayed(Duration(seconds: 1));
      _user = User(
        id: userId,
        name: 'John Doe',
        email: 'john.doe@example.com',
      );
      _isLoading = false;
      notifyListeners();
    } catch (e) {
      _isLoading = false;
      _error = e.toString();
      notifyListeners();
    }
  }
}

// 定义View
class UserProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => UserViewModel(),
      child: Scaffold(
        appBar: AppBar(title: Text('用户资料')),
        body: Consumer<UserViewModel>(
          builder: (context, viewModel, child) {
            if (viewModel.isLoading) {
              return Center(child: CircularProgressIndicator());
            }
            
            if (viewModel.error != null) {
              return Center(child: Text('错误: ${viewModel.error}'));
            }
            
            final user = viewModel.user;
            if (user == null) {
              return Center(
                child: ElevatedButton(
                  onPressed: () => viewModel.fetchUser('1'),
                  child: Text('加载用户数据'),
                ),
              );
            }
            
            return Padding(
              padding: EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('ID: ${user.id}', style: TextStyle(fontSize: 18)),
                  SizedBox(height: 8),
                  Text('姓名: ${user.name}', style: TextStyle(fontSize: 18)),
                  SizedBox(height: 8),
                  Text('邮箱: ${user.email}', style: TextStyle(fontSize: 18)),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}
2.1.2 使用GetX实现MVVM

GetX是一个轻量且强大的Flutter状态管理库,它提供了依赖注入、路由管理和状态管理等功能,非常适合实现MVVM架构。

dart 复制代码
// 定义Model
class User {
  final String id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
}

// 定义ViewModel
class UserController extends GetxController {
  final Rx<User?> _user = Rx<User?>(null);
  final RxBool _isLoading = false.obs;
  final Rx<String?> _error = Rx<String?>(null);
  
  User? get user => _user.value;
  bool get isLoading => _isLoading.value;
  String? get error => _error.value;
  
  Future<void> fetchUser(String userId) async {
    _isLoading.value = true;
    _error.value = null;
    
    try {
      // 模拟网络请求
      await Future.delayed(Duration(seconds: 1));
      _user.value = User(
        id: userId,
        name: 'John Doe',
        email: 'john.doe@example.com',
      );
      _isLoading.value = false;
    } catch (e) {
      _isLoading.value = false;
      _error.value = e.toString();
    }
  }
}

// 定义View
class UserProfileScreen extends StatelessWidget {
  final UserController controller = Get.put(UserController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用户资料')),
      body: Obx(() {
        if (controller.isLoading) {
          return Center(child: CircularProgressIndicator());
        }
        
        if (controller.error != null) {
          return Center(child: Text('错误: ${controller.error}'));
        }
        
        final user = controller.user;
        if (user == null) {
          return Center(
            child: ElevatedButton(
              onPressed: () => controller.fetchUser('1'),
              child: Text('加载用户数据'),
            ),
          );
        }
        
        return Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('ID: ${user.id}', style: TextStyle(fontSize: 18)),
              SizedBox(height: 8),
              Text('姓名: ${user.name}', style: TextStyle(fontSize: 18)),
              SizedBox(height: 8),
              Text('邮箱: ${user.email}', style: TextStyle(fontSize: 18)),
            ],
          ),
        );
      }),
    );
  }
}
2.1.3 使用Riverpod实现MVVM

Riverpod是Provider的重新设计版本,它解决了Provider的一些限制,提供了更强大的依赖管理和状态管理功能。

dart 复制代码
// 定义Model
class User {
  final String id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
}

// 定义状态
class UserState {
  final User? user;
  final bool isLoading;
  final String? error;
  
  UserState({this.user, this.isLoading = false, this.error});
  
  UserState copyWith({User? user, bool? isLoading, String? error}) {
    return UserState(
      user: user ?? this.user,
      isLoading: isLoading ?? this.isLoading,
      error: error ?? this.error,
    );
  }
}

// 定义ViewModel
class UserNotifier extends StateNotifier<UserState> {
  UserNotifier() : super(UserState());
  
  Future<void> fetchUser(String userId) async {
    state = state.copyWith(isLoading: true, error: null);
    
    try {
      // 模拟网络请求
      await Future.delayed(Duration(seconds: 1));
      final user = User(
        id: userId,
        name: 'John Doe',
        email: 'john.doe@example.com',
      );
      state = state.copyWith(user: user, isLoading: false);
    } catch (e) {
      state = state.copyWith(isLoading: false, error: e.toString());
    }
  }
}

// 定义Provider
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
  return UserNotifier();
});

// 定义View
class UserProfileScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userState = ref.watch(userProvider);
    
    return Scaffold(
      appBar: AppBar(title: Text('用户资料')),
      body: Builder(
        builder: (context) {
          if (userState.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (userState.error != null) {
            return Center(child: Text('错误: ${userState.error}'));
          }
          
          final user = userState.user;
          if (user == null) {
            return Center(
              child: ElevatedButton(
                onPressed: () => ref.read(userProvider.notifier).fetchUser('1'),
                child: Text('加载用户数据'),
              ),
            );
          }
          
          return Padding(
            padding: EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('ID: ${user.id}', style: TextStyle(fontSize: 18)),
                SizedBox(height: 8),
                Text('姓名: ${user.name}', style: TextStyle(fontSize: 18)),
                SizedBox(height: 8),
                Text('邮箱: ${user.email}', style: TextStyle(fontSize: 18)),
              ],
            ),
          );
        },
      ),
    );
  }
}

2.2 MVVM架构中的依赖注入

依赖注入是MVVM架构中的重要概念,它使得组件之间的耦合度降低,提高了代码的可测试性和可维护性。在Flutter中,可以通过以下几种方式实现依赖注入:

2.2.1 使用Provider进行依赖注入
dart 复制代码
void main() {
  runApp(MultiProvider(
    providers: [
      Provider<ApiService>(
        create: (_) => ApiService(),
      ),
      ProxyProvider<ApiService, UserRepository>(
        update: (_, apiService, __) => UserRepository(apiService),
      ),
      ChangeNotifierProxyProvider<UserRepository, UserViewModel>(
        create: (_) => UserViewModel(null),
        update: (_, repository, viewModel) => UserViewModel(repository),
      ),
    ],
    child: MyApp(),
  ));
}
2.2.2 使用GetX进行依赖注入
dart 复制代码
void main() {
  // 注册依赖
  Get.put(ApiService());
  Get.put(UserRepository(Get.find<ApiService>()));
  Get.put(UserController(Get.find<UserRepository>()));
  
  runApp(GetMaterialApp(
    home: UserProfileScreen(),
  ));
}
2.2.3 使用Riverpod进行依赖注入
dart 复制代码
// 定义Provider
final apiServiceProvider = Provider((ref) => ApiService());

final userRepositoryProvider = Provider((ref) {
  final apiService = ref.watch(apiServiceProvider);
  return UserRepository(apiService);
});

final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
  final repository = ref.watch(userRepositoryProvider);
  return UserNotifier(repository);
});

void main() {
  runApp(ProviderScope(
    child: MyApp(),
  ));
}

三、MVVM架构实战案例:社交媒体应用

接下来,我们将通过一个社交媒体应用的实例,展示如何在Flutter中应用MVVM架构。这个应用将包含用户认证、帖子列表和帖子详情等功能。

3.1 项目结构

复制代码
lib/
├── main.dart
├── app.dart
├── models/
│   ├── user.dart
│   ├── post.dart
│   └── comment.dart
├── services/
│   ├── api_service.dart
│   ├── auth_service.dart
│   └── storage_service.dart
├── repositories/
│   ├── user_repository.dart
│   └── post_repository.dart
├── viewmodels/
│   ├── auth_viewmodel.dart
│   ├── post_list_viewmodel.dart
│   └── post_detail_viewmodel.dart
└── views/
    ├── auth/
    │   ├── login_screen.dart
    │   └── register_screen.dart
    ├── posts/
    │   ├── post_list_screen.dart
    │   └── post_detail_screen.dart
    └── widgets/
        ├── post_card.dart
        └── comment_list.dart

3.2 实现Model层

dart 复制代码
// lib/models/user.dart
class User {
  final String id;
  final String username;
  final String email;
  final String? avatarUrl;
  
  User({
    required this.id,
    required this.username,
    required this.email,
    this.avatarUrl,
  });
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      username: json['username'],
      email: json['email'],
      avatarUrl: json['avatar_url'],
    );
  }
  
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'username': username,
      'email': email,
      'avatar_url': avatarUrl,
    };
  }
}

// lib/models/post.dart
class Post {
  final String id;
  final String userId;
  final String title;
  final String content;
  final DateTime createdAt;
  final int likesCount;
  final int commentsCount;
  
  Post({
    required this.id,
    required this.userId,
    required this.title,
    required this.content,
    required this.createdAt,
    this.likesCount = 0,
    this.commentsCount = 0,
  });
  
  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      userId: json['user_id'],
      title: json['title'],
      content: json['content'],
      createdAt: DateTime.parse(json['created_at']),
      likesCount: json['likes_count'] ?? 0,
      commentsCount: json['comments_count'] ?? 0,
    );
  }
  
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'user_id': userId,
      'title': title,
      'content': content,
      'created_at': createdAt.toIso8601String(),
      'likes_count': likesCount,
      'comments_count': commentsCount,
    };
  }
}

// lib/models/comment.dart
class Comment {
  final String id;
  final String postId;
  final String userId;
  final String content;
  final DateTime createdAt;
  
  Comment({
    required this.id,
    required this.postId,
    required this.userId,
    required this.content,
    required this.createdAt,
  });
  
  factory Comment.fromJson(Map<String, dynamic> json) {
    return Comment(
      id: json['id'],
      postId: json['post_id'],
      userId: json['user_id'],
      content: json['content'],
      createdAt: DateTime.parse(json['created_at']),
    );
  }
  
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'post_id': postId,
      'user_id': userId,
      'content': content,
      'created_at': createdAt.toIso8601String(),
    };
  }
}

3.3 实现Service层

dart 复制代码
// lib/services/api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class ApiService {
  final String baseUrl = 'https://api.example.com';
  final Map<String, String> _headers = {'Content-Type': 'application/json'};
  String? _token;
  
  void setToken(String token) {
    _token = token;
    _headers['Authorization'] = 'Bearer $token';
  }
  
  void clearToken() {
    _token = null;
    _headers.remove('Authorization');
  }
  
  Future<Map<String, dynamic>> get(String endpoint) async {
    final response = await http.get(
      Uri.parse('$baseUrl/$endpoint'),
      headers: _headers,
    );
    
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return json.decode(response.body);
    } else {
      throw Exception('API Error: ${response.statusCode} ${response.body}');
    }
  }
  
  Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {
    final response = await http.post(
      Uri.parse('$baseUrl/$endpoint'),
      headers: _headers,
      body: json.encode(data),
    );
    
    if (response.statusCode >= 200 && response.statusCode < 300) {
      return json.decode(response.body);
    } else {
      throw Exception('API Error: ${response.statusCode} ${response.body}');
    }
  }
  
  // 其他HTTP方法(PUT, DELETE等)...
}

// lib/services/auth_service.dart
import 'package:shared_preferences/shared_preferences.dart';
import '../services/api_service.dart';
import '../models/user.dart';

class AuthService {
  final ApiService _apiService;
  final String _tokenKey = 'auth_token';
  final String _userKey = 'user_data';
  
  AuthService(this._apiService);
  
  Future<User> login(String email, String password) async {
    final response = await _apiService.post('auth/login', {
      'email': email,
      'password': password,
    });
    
    final token = response['token'];
    final userData = response['user'];
    
    // 保存token和用户数据
    await _saveToken(token);
    await _saveUserData(userData);
    
    _apiService.setToken(token);
    
    return User.fromJson(userData);
  }
  
  Future<User> register(String username, String email, String password) async {
    final response = await _apiService.post('auth/register', {
      'username': username,
      'email': email,
      'password': password,
    });
    
    final token = response['token'];
    final userData = response['user'];
    
    // 保存token和用户数据
    await _saveToken(token);
    await _saveUserData(userData);
    
    _apiService.setToken(token);
    
    return User.fromJson(userData);
  }
  
  Future<void> logout() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_tokenKey);
    await prefs.remove(_userKey);
    
    _apiService.clearToken();
  }
  
  Future<bool> isLoggedIn() async {
    final token = await _getToken();
    return token != null;
  }
  
  Future<User?> getCurrentUser() async {
    final userData = await _getUserData();
    if (userData == null) return null;
    
    return User.fromJson(userData);
  }
  
  Future<void> _saveToken(String token) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_tokenKey, token);
  }
  
  Future<String?> _getToken() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString(_tokenKey);
  }
  
  Future<void> _saveUserData(Map<String, dynamic> userData) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_userKey, json.encode(userData));
  }
  
  Future<Map<String, dynamic>?> _getUserData() async {
    final prefs = await SharedPreferences.getInstance();
    final userDataString = prefs.getString(_userKey);
    
    if (userDataString == null) return null;
    
    return json.decode(userDataString) as Map<String, dynamic>;
  }
}

3.4 实现Repository层

dart 复制代码
// lib/repositories/post_repository.dart
import '../models/post.dart';
import '../models/comment.dart';
import '../services/api_service.dart';

class PostRepository {
  final ApiService _apiService;
  
  PostRepository(this._apiService);
  
  Future<List<Post>> getPosts({int page = 1, int limit = 10}) async {
    final response = await _apiService.get('posts?page=$page&limit=$limit');
    final List<dynamic> postsData = response['data'];
    
    return postsData.map((data) => Post.fromJson(data)).toList();
  }
  
  Future<Post> getPostById(String id) async {
    final response = await _apiService.get('posts/$id');
    return Post.fromJson(response['data']);
  }
  
  Future<List<Comment>> getCommentsByPostId(String postId) async {
    final response = await _apiService.get('posts/$postId/comments');
    final List<dynamic> commentsData = response['data'];
    
    return commentsData.map((data) => Comment.fromJson(data)).toList();
  }
  
  Future<Post> createPost(String title, String content) async {
    final response = await _apiService.post('posts', {
      'title': title,
      'content': content,
    });
    
    return Post.fromJson(response['data']);
  }
  
  Future<Comment> addComment(String postId, String content) async {
    final response = await _apiService.post('posts/$postId/comments', {
      'content': content,
    });
    
    return Comment.fromJson(response['data']);
  }
  
  Future<void> likePost(String postId) async {
    await _apiService.post('posts/$postId/like', {});
  }
}

3.5 实现ViewModel层

dart 复制代码
// lib/viewmodels/auth_viewmodel.dart
import 'package:flutter/foundation.dart';
import '../models/user.dart';
import '../services/auth_service.dart';

class AuthViewModel extends ChangeNotifier {
  final AuthService _authService;
  
  User? _currentUser;
  bool _isLoading = false;
  String? _error;
  
  AuthViewModel(this._authService) {
    _init();
  }
  
  User? get currentUser => _currentUser;
  bool get isLoading => _isLoading;
  bool get isLoggedIn => _currentUser != null;
  String? get error => _error;
  
  Future<void> _init() async {
    _isLoading = true;
    notifyListeners();
    
    try {
      _currentUser = await _authService.getCurrentUser();
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
  
  Future<bool> login(String email, String password) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      _currentUser = await _authService.login(email, password);
      _isLoading = false;
      notifyListeners();
      return true;
    } catch (e) {
      _isLoading = false;
      _error = e.toString();
      notifyListeners();
      return false;
    }
  }
  
  Future<bool> register(String username, String email, String password) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      _currentUser = await _authService.register(username, email, password);
      _isLoading = false;
      notifyListeners();
      return true;
    } catch (e) {
      _isLoading = false;
      _error = e.toString();
      notifyListeners();
      return false;
    }
  }
  
  Future<void> logout() async {
    _isLoading = true;
    notifyListeners();
    
    try {
      await _authService.logout();
      _currentUser = null;
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

// lib/viewmodels/post_list_viewmodel.dart
import 'package:flutter/foundation.dart';
import '../models/post.dart';
import '../repositories/post_repository.dart';

class PostListViewModel extends ChangeNotifier {
  final PostRepository _postRepository;
  
  List<Post> _posts = [];
  bool _isLoading = false;
  String? _error;
  
  PostListViewModel(this._postRepository);
  
  List<Post> get posts => _posts;
  bool get isLoading => _isLoading;
  String? get error => _error;
  
  Future<void> fetchPosts() async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      _posts = await _postRepository.getPosts();
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
  
  Future<void> refresh() async {
    await fetchPosts();
  }
}

// lib/viewmodels/post_detail_viewmodel.dart
class PostDetailViewModel extends ChangeNotifier {
  final PostRepository _postRepository;
  
  Post? _post;
  List<Comment> _comments = [];
  bool _isLoading = false;
  String? _error;
  
  PostDetailViewModel(this._postRepository);
  
  Post? get post => _post;
  List<Comment> get comments => _comments;
  bool get isLoading => _isLoading;
  String? get error => _error;
  
  Future<void> loadPostDetails(String postId) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      // 并行请求帖子详情和评论
      final results = await Future.wait([
        _postRepository.getPostById(postId),
        _postRepository.getCommentsByPostId(postId),
      ]);
      
      _post = results[0] as Post;
      _comments = results[1] as List<Comment>;
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
  
  Future<void> addComment(String postId, String content) async {
    try {
      final newComment = await _postRepository.addComment(postId, content);
      _comments.insert(0, newComment);
      notifyListeners();
    } catch (e) {
      _error = '发送评论失败: $e';
      notifyListeners();
    }
  }
}

// lib/views/auth/login_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../viewmodels/auth_viewmodel.dart';
import '../posts/post_list_screen.dart';

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('登录')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Consumer<AuthViewModel>(
          builder: (context, viewModel, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextField(
                  controller: _emailController,
                  decoration: InputDecoration(labelText: '邮箱'),
                ),
                SizedBox(height: 16),
                TextField(
                  controller: _passwordController,
                  decoration: InputDecoration(labelText: '密码'),
                  obscureText: true,
                ),
                SizedBox(height: 24),
                if (viewModel.isLoading)
                  CircularProgressIndicator()
                else
                  ElevatedButton(
                    onPressed: () async {
                      final success = await viewModel.login(
                        _emailController.text,
                        _passwordController.text,
                      );
                      
                      if (success) {
                        Navigator.pushReplacement(
                          context,
                          MaterialPageRoute(builder: (_) => PostListScreen()),
                        );
                      } else if (viewModel.error != null) {
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(content: Text(viewModel.error!)),
                        );
                      }
                    },
                    child: Text('登录'),
                  ),
              ],
            );
          },
        ),
      ),
    );
  }
}

// lib/views/posts/post_list_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../viewmodels/post_list_viewmodel.dart';
import '../../viewmodels/auth_viewmodel.dart';
import '../auth/login_screen.dart';

class PostListScreen extends StatefulWidget {
  @override
  _PostListScreenState createState() => _PostListScreenState();
}

class _PostListScreenState extends State<PostListScreen> {
  @override
  void initState() {
    super.initState();
    // 初始加载数据
    Future.microtask(() => 
      Provider.of<PostListViewModel>(context, listen: false).fetchPosts()
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('帖子列表'),
        actions: [
          IconButton(
            icon: Icon(Icons.exit_to_app),
            onPressed: () {
              Provider.of<AuthViewModel>(context, listen: false).logout();
              Navigator.pushReplacement(
                context,
                MaterialPageRoute(builder: (_) => LoginScreen()),
              );
            },
          ),
        ],
      ),
      body: Consumer<PostListViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (viewModel.error != null) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('加载失败: ${viewModel.error}'),
                  ElevatedButton(
                    onPressed: () => viewModel.refresh(),
                    child: Text('重试'),
                  ),
                ],
              ),
            );
          }
          
          return RefreshIndicator(
            onRefresh: viewModel.refresh,
            child: ListView.builder(
              itemCount: viewModel.posts.length,
              itemBuilder: (context, index) {
                final post = viewModel.posts[index];
                return Card(
                  margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  child: ListTile(
                    title: Text(post.title),
                    subtitle: Text(
                      post.content,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    trailing: Icon(Icons.chevron_right),
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (_) => PostDetailScreen(postId: post.id),
                        ),
                      );
                    },
                  ),
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 导航到发布页面
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

// lib/views/posts/post_detail_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../viewmodels/post_detail_viewmodel.dart';

class PostDetailScreen extends StatefulWidget {
  final String postId;

  const PostDetailScreen({Key? key, required this.postId}) : super(key: key);

  @override
  _PostDetailScreenState createState() => _PostDetailScreenState();
}

class _PostDetailScreenState extends State<PostDetailScreen> {
  final _commentController = TextEditingController();

  @override
  void initState() {
    super.initState();
    Future.microtask(() =>
      Provider.of<PostDetailViewModel>(context, listen: false).loadPostDetails(widget.postId)
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('帖子详情')),
      body: Consumer<PostDetailViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

          if (viewModel.error != null) {
            return Center(child: Text('加载失败: ${viewModel.error}'));
          }

          final post = viewModel.post;
          if (post == null) {
            return Center(child: Text('未找到帖子'));
          }

          return Column(
            children: [
              Expanded(
                child: ListView(
                  padding: EdgeInsets.all(16.0),
                  children: [
                    Text(post.title, style: Theme.of(context).textTheme.headlineMedium),
                    SizedBox(height: 8),
                    Text(
                      '作者ID: ${post.userId}  发布于: ${post.createdAt.toString().split('.')[0]}',
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                    SizedBox(height: 16),
                    Text(post.content, style: Theme.of(context).textTheme.bodyLarge),
                    SizedBox(height: 24),
                    Text('评论 (${viewModel.comments.length})', style: Theme.of(context).textTheme.titleMedium),
                    Divider(),
                    ...viewModel.comments.map((comment) => ListTile(
                      title: Text(comment.content),
                      subtitle: Text(comment.createdAt.toString().split('.')[0]),
                    )).toList(),
                  ],
                ),
              ),
              Padding(
                padding: EdgeInsets.all(8.0),
                child: Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _commentController,
                        decoration: InputDecoration(
                          hintText: '写下你的评论...',
                          border: OutlineInputBorder(),
                        ),
                      ),
                    ),
                    SizedBox(width: 8),
                    IconButton(
                      icon: Icon(Icons.send),
                      onPressed: () {
                        if (_commentController.text.isNotEmpty) {
                          viewModel.addComment(post.id, _commentController.text);
                          _commentController.clear();
                        }
                      },
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

四、常见面试题解析

4.1 MVVM与MVC、MVP的主要区别是什么?

  • MVC (Model-View-Controller):Controller负责处理用户输入并更新Model和View。View和Model之间存在耦合。
  • MVP (Model-View-Presenter):Presenter介于View和Model之间,View和Model完全解耦。View通过接口与Presenter交互。
  • MVVM (Model-View-ViewModel):ViewModel与View通过数据绑定(Data Binding)进行通信。ViewModel不持有View的引用,而是暴露数据流(Stream/ChangeNotifier),View订阅这些流。这减少了View和ViewModel之间的耦合,并且使得ViewModel更容易测试。

4.2 在Flutter中,ViewModel应该持有Context吗?


不建议。ViewModel应该尽量保持纯粹的业务逻辑,不依赖于UI框架的具体实现(如BuildContext)。

  • 原因:持有Context会导致ViewModel与具体的Widget树耦合,难以进行单元测试,并且可能导致内存泄漏(如果Context对应的Widget被销毁但ViewModel仍存活)。
  • 解决方案:如果需要导航或显示SnackBar等操作,可以使用全局的NavigationKey,或者通过回调/流的方式通知UI层去执行这些操作。

4.3 使用MVVM后,是否还需要使用setState

虽然MVVM通常结合状态管理库(如Provider、GetX)使用,但在某些情况下,setState仍然是有用的。

  • 局部短暂状态 :对于仅与当前Widget相关的、不需要跨组件共享的、非业务逻辑的状态(如动画控制器状态、TextField的焦点状态、展开/收起UI的标志位),使用setState是最简单且高效的方式。
  • 混合使用 :MVVM处理业务状态,setState处理UI内部的临时交互状态,两者并不冲突。

4.4 如何在MVVM中处理导航?

有几种常见的方法:

  1. UI层监听状态 :ViewModel暴露一个状态或事件流(如navigateToFile),UI层(View)监听这个流并执行导航。
  2. 依赖注入Navigation Service :创建一个不依赖于Context的NavigationService(通常使用GlobalKey<NavigatorState>),并将其注入到ViewModel中。
  3. 回调函数:View调用ViewModel的方法时传入回调函数,ViewModel在处理完逻辑后执行回调,由View进行导航。

五、总结

MVVM架构通过将UI逻辑与业务逻辑分离,极大地提高了Flutter应用的可维护性和可测试性。

  • 核心优势:View与Model解耦,ViewModel易于测试,代码结构清晰。
  • 适用场景:适合中大型应用,或者业务逻辑较为复杂的页面。
  • 最佳实践:配合Provider、Riverpod或GetX等状态管理库使用,可以事半功倍。

掌握MVVM架构不仅能帮助我们写出更好的代码,也是迈向高级Flutter开发者的必经之路。

相关推荐
熊出没1 小时前
微服务架构介绍
微服务·云原生·架构
Xの哲學1 小时前
Linux内核数据结构:设计哲学与实现机制
linux·服务器·算法·架构·边缘计算
Hernon1 小时前
微服务架构设计 - 单体架构
微服务·云原生·架构·系统架构
AskHarries1 小时前
半天、200 元,我把自己的 App 做出来并上架了 App Store
flutter
陈朝晖SHS1 小时前
Flutter项目结合iOS OC原生页面禁止截屏
flutter·ios
Wokoo71 小时前
C/S 架构与 B/S 架构:核心差异 + 选型指南
分布式·后端·中间件·架构
lew-yu1 小时前
当前开源旗舰LLM主流架构介绍
架构·大模型·llm
2501_941982051 小时前
API + RPA混合架构:如何构建高并发、可扩展的企业微信自动化平台
架构·企业微信·rpa
RFSOC+FPGA2 小时前
林肯实验室文献解读(1)-相控阵列架构实现可扩展的集成感知和通信
算法·架构