Skip to content

ViewModel 生成

使用 flu-cli add viewmodel 命令生成视图模型,管理页面状态和业务逻辑。

基本用法

bash
flu-cli add viewmodel <name> [options]
flu-cli add vm <name> [options]
flu-cli a vm <name> [options]

参数

参数说明
-f, --feature所属功能模块

生成的代码

dart
import 'package:flutter/foundation.dart';

class HomeViewModel extends ChangeNotifier {
  // 状态
  bool _isLoading = false;
  bool get isLoading => _isLoading;

  // 初始化
  HomeViewModel() {
    _init();
  }

  void _init() {
    // 初始化逻辑
  }

  // 业务方法
  Future<void> loadData() async {
    _isLoading = true;
    notifyListeners();

    try {
      // 加载数据
      await Future.delayed(const Duration(seconds: 1));
    } catch (e) {
      debugPrint('Error: $e');
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  @override
  void dispose() {
    // 清理资源
    super.dispose();
  }
}

ViewModel 职责

1. 状态管理

管理页面的所有状态:

dart
class UserListViewModel extends ChangeNotifier {
  List<User> _users = [];
  List<User> get users => _users;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _error;
  String? get error => _error;

  int _selectedIndex = 0;
  int get selectedIndex => _selectedIndex;
}

2. 业务逻辑

处理业务逻辑和数据操作:

dart
class UserListViewModel extends ChangeNotifier {
  final UserService _service = UserService();

  Future<void> loadUsers() async {
    _isLoading = true;
    _error = null;
    notifyListeners();

    try {
      _users = await _service.getUsers();
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> deleteUser(String id) async {
    try {
      await _service.deleteUser(id);
      _users.removeWhere((user) => user.id == id);
      notifyListeners();
    } catch (e) {
      _error = e.toString();
      notifyListeners();
    }
  }
}

3. UI 交互

处理用户交互:

dart
class UserListViewModel extends ChangeNotifier {
  void selectUser(int index) {
    _selectedIndex = index;
    notifyListeners();
  }

  void toggleFavorite(String userId) {
    final index = _users.indexWhere((u) => u.id == userId);
    if (index != -1) {
      _users[index] = _users[index].copyWith(
        isFavorite: !_users[index].isFavorite,
      );
      notifyListeners();
    }
  }
}

使用 ViewModel

在 StatelessWidget 中

dart
class UserListPage extends StatelessWidget {
  const UserListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => UserListViewModel()..loadUsers(),
      child: Consumer<UserListViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          return ListView.builder(
            itemCount: viewModel.users.length,
            itemBuilder: (context, index) {
              final user = viewModel.users[index];
              return ListTile(
                title: Text(user.name),
                onTap: () => viewModel.selectUser(index),
              );
            },
          );
        },
      ),
    );
  }
}

在 StatefulWidget 中

dart
class UserListPage extends StatefulWidget {
  const UserListPage({super.key});

  @override
  State<UserListPage> createState() => _UserListPageState();
}

class _UserListPageState extends State<UserListPage> {
  late final UserListViewModel _viewModel;

  @override
  void initState() {
    super.initState();
    _viewModel = UserListViewModel();
    _viewModel.addListener(_onViewModelChanged);
    _viewModel.loadUsers();
  }

  void _onViewModelChanged() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    if (_viewModel.isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    return ListView.builder(
      itemCount: _viewModel.users.length,
      itemBuilder: (context, index) {
        final user = _viewModel.users[index];
        return ListTile(
          title: Text(user.name),
          onTap: () => _viewModel.selectUser(index),
        );
      },
    );
  }

  @override
  void dispose() {
    _viewModel.removeListener(_onViewModelChanged);
    _viewModel.dispose();
    super.dispose();
  }
}

最佳实践

1. 单一职责

每个 ViewModel 只负责一个页面或功能:

bash
# ✅ 好的做法
flu-cli a vm user_list -f user
flu-cli a vm user_detail -f user
flu-cli a vm user_edit -f user

# ❌ 避免
flu-cli a vm user  # 太宽泛

2. 状态管理

使用私有变量和公共 getter:

dart
class UserListViewModel extends ChangeNotifier {
  // 私有状态
  List<User> _users = [];
  bool _isLoading = false;
  String? _error;

  // 公共 getter
  List<User> get users => _users;
  bool get isLoading => _isLoading;
  String? get error => _error;

  // 公共方法修改状态
  Future<void> loadUsers() async {
    _setLoading(true);
    // ...
  }

  void _setLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }
}

3. 错误处理

统一的错误处理:

dart
class UserListViewModel extends ChangeNotifier {
  String? _error;
  String? get error => _error;

  Future<void> loadUsers() async {
    _error = null;
    notifyListeners();

    try {
      _users = await _service.getUsers();
    } catch (e) {
      _error = '加载失败: ${e.toString()}';
      debugPrint('Error loading users: $e');
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  void clearError() {
    _error = null;
    notifyListeners();
  }
}

4. 资源清理

在 dispose 中清理资源:

dart
class UserListViewModel extends ChangeNotifier {
  final UserService _service = UserService();
  Timer? _refreshTimer;

  UserListViewModel() {
    _startAutoRefresh();
  }

  void _startAutoRefresh() {
    _refreshTimer = Timer.periodic(
      const Duration(minutes: 5),
      (_) => loadUsers(),
    );
  }

  @override
  void dispose() {
    _refreshTimer?.cancel();
    _service.dispose();
    super.dispose();
  }
}

完整示例

创建 ViewModel

bash
flu-cli a vm user_list -f user

完善 ViewModel

dart
import 'package:flutter/foundation.dart';
import '../models/user_model.dart';
import '../services/user_service.dart';

class UserListViewModel extends ChangeNotifier {
  final UserService _service = UserService();

  // 状态
  List<User> _users = [];
  List<User> get users => _users;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _error;
  String? get error => _error;

  String _searchQuery = '';
  String get searchQuery => _searchQuery;

  // 计算属性
  List<User> get filteredUsers {
    if (_searchQuery.isEmpty) return _users;
    return _users.where((user) =>
      user.name.toLowerCase().contains(_searchQuery.toLowerCase())
    ).toList();
  }

  // 初始化
  UserListViewModel() {
    loadUsers();
  }

  // 加载用户
  Future<void> loadUsers() async {
    _setLoading(true);
    _setError(null);

    try {
      _users = await _service.getUsers();
    } catch (e) {
      _setError('加载失败: ${e.toString()}');
      debugPrint('Error loading users: $e');
    } finally {
      _setLoading(false);
    }
  }

  // 搜索
  void search(String query) {
    _searchQuery = query;
    notifyListeners();
  }

  // 删除用户
  Future<void> deleteUser(String id) async {
    try {
      await _service.deleteUser(id);
      _users.removeWhere((user) => user.id == id);
      notifyListeners();
    } catch (e) {
      _setError('删除失败: ${e.toString()}');
    }
  }

  // 刷新
  Future<void> refresh() async {
    await loadUsers();
  }

  // 私有方法
  void _setLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  void _setError(String? value) {
    _error = value;
    notifyListeners();
  }

  @override
  void dispose() {
    _service.dispose();
    super.dispose();
  }
}

下一步

Released under the MIT License.