架构设计模式: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架构的核心原则
- 关注点分离:UI逻辑与业务逻辑分离,使代码更加清晰和易于维护。
- 数据绑定:View和ViewModel之间通过数据绑定机制实现自动同步。
- 依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖于抽象。
- 可测试性:由于业务逻辑与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中处理导航?
答 :
有几种常见的方法:
- UI层监听状态 :ViewModel暴露一个状态或事件流(如
navigateToFile),UI层(View)监听这个流并执行导航。 - 依赖注入Navigation Service :创建一个不依赖于Context的NavigationService(通常使用
GlobalKey<NavigatorState>),并将其注入到ViewModel中。 - 回调函数:View调用ViewModel的方法时传入回调函数,ViewModel在处理完逻辑后执行回调,由View进行导航。
五、总结
MVVM架构通过将UI逻辑与业务逻辑分离,极大地提高了Flutter应用的可维护性和可测试性。
- 核心优势:View与Model解耦,ViewModel易于测试,代码结构清晰。
- 适用场景:适合中大型应用,或者业务逻辑较为复杂的页面。
- 最佳实践:配合Provider、Riverpod或GetX等状态管理库使用,可以事半功倍。
掌握MVVM架构不仅能帮助我们写出更好的代码,也是迈向高级Flutter开发者的必经之路。