Files
oneapp_docs/basic_utils/base_mvvm.md
2025-09-24 14:08:54 +08:00

13 KiB
Raw Permalink Blame History

Base MVVM - 基础MVVM架构模块文档

模块概述

base_mvvm 是 OneApp 基础工具模块群中的MVVM架构基础模块提供了完整的Model-View-ViewModel架构模式实现。该模块封装了状态管理、数据绑定、Provider模式等核心功能为应用提供了统一的架构规范和基础组件。

基本信息

  • 模块名称: base_mvvm
  • 模块路径: oneapp_basic_utils/base_mvvm
  • 类型: Flutter Package Module
  • 主要功能: MVVM架构基础、状态管理、数据绑定

核心特性

  • 状态管理: 基于Provider的响应式状态管理
  • 视图状态: 统一的页面状态管理(idle/busy/empty/error)
  • 列表刷新: 封装下拉刷新和上拉加载更多
  • 数据绑定: 双向数据绑定支持
  • 错误处理: 统一的错误类型和处理机制
  • 日志工具: 集成日志记录功能

目录结构

base_mvvm/
├── lib/
│   ├── base_mvvm.dart              # 模块入口文件
│   ├── provider/                   # Provider相关
│   │   ├── provider_widget.dart    # Provider封装组件
│   │   ├── view_state_model.dart   # 视图状态模型
│   │   ├── view_state_list_model.dart        # 列表状态模型
│   │   ├── view_state_refresh_list_model.dart # 刷新列表模型
│   │   └── view_state.dart         # 状态枚举定义
│   ├── utils/                      # 工具类
│   │   ├── logs/                   # 日志工具
│   │   │   └── log_utils.dart
│   │   └── rxbus.dart              # 事件总线
│   └── widgets/                    # 基础组件
│       ├── glides/                 # 图片组件
│       │   └── glide_image_view.dart
│       └── refresh/                # 刷新组件
│           └── base_easy_refresh.dart
└── pubspec.yaml                    # 依赖配置

核心架构组件

1. 视图状态枚举 (ViewState)

定义了应用中通用的页面状态:

/// 页面状态类型
enum ViewState {
  idle,    // 空闲状态
  busy,    // 加载中
  empty,   // 无数据
  error,   // 加载失败
}

/// 错误类型
enum ViewStateErrorType {
  none,
  defaultError,
  networkTimeOutError,    // 网络超时
  disconnectException,    // 断网
  noNetworkSignal,       // 无网络信号
}

2. 基础视图状态模型 (ViewStateModel)

所有ViewModel的基础类提供统一的状态管理

class ViewStateModel with ChangeNotifier {
  /// 防止页面销毁后异步任务才完成导致报错
  bool _disposed = false;

  /// 当前页面状态默认为idle
  ViewState _viewState = ViewState.idle;
  
  /// 错误类型
  ViewStateErrorType _viewStateError = ViewStateErrorType.none;

  /// 构造函数,可指定初始状态
  ViewStateModel({ViewState? viewState})
      : _viewState = viewState ?? ViewState.idle;

  // 状态获取器
  ViewState get viewState => _viewState;
  ViewStateErrorType get viewStateError => _viewStateError;

  // 状态判断方法
  bool get isBusy => viewState == ViewState.busy;
  bool get isIdle => viewState == ViewState.idle;
  bool get isEmpty => viewState == ViewState.empty;
  bool get isError => viewState == ViewState.error;

  // 状态设置方法
  void setIdle() => viewState = ViewState.idle;
  void setBusy() => viewState = ViewState.busy;
  void setEmpty() => viewState = ViewState.empty;

  @override
  void notifyListeners() {
    if (!_disposed) {
      super.notifyListeners();
    }
  }

  @override
  void dispose() {
    _disposed = true;
    super.dispose();
  }
}

3. Provider封装组件 (ProviderWidget)

简化Provider使用的封装组件

class ProviderWidget<T extends ChangeNotifier> extends StatefulWidget {
  final ValueWidgetBuilder<T> builder;
  final T model;
  final Widget? child;
  final Function(T model)? onModelReady;
  final bool autoDispose;

  const ProviderWidget({
    super.key,
    required this.builder,
    required this.model,
    this.child,
    this.onModelReady,
    this.autoDispose = true
  });

  @override
  _ProviderWidgetState<T> createState() => _ProviderWidgetState<T>();
}

class _ProviderWidgetState<T extends ChangeNotifier>
    extends State<ProviderWidget<T>> {
  late T model;

  @override
  void initState() {
    model = widget.model;
    widget.onModelReady?.call(model);
    super.initState();
  }

  @override
  void dispose() {
    if (widget.autoDispose) {
      model.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>.value(
      value: model,
      child: Consumer<T>(builder: widget.builder, child: widget.child)
    );
  }
}

4. 多Provider支持 (ProviderWidget2)

支持同时管理两个Model的组件

class ProviderWidget2<A extends ChangeNotifier, B extends ChangeNotifier>
    extends StatefulWidget {
  final Widget Function(BuildContext context, A model1, B model2, Widget? child) builder;
  final A model1;
  final B model2;
  final Widget? child;
  final Function(A model1, B model2)? onModelReady;
  final bool autoDispose;

  // ... 实现与ProviderWidget类似支持双Model管理
}

5. 刷新列表状态模型 (ViewStateRefreshListModel)

专门用于处理列表数据的刷新和加载更多功能:

abstract class ViewStateRefreshListModel<T> extends ViewStateListModel<T> {
  /// 分页配置
  static const int pageNumFirst = 1;
  static const int pageSize = 10;

  /// 列表刷新控制器
  final EasyRefreshController _refreshController;
  EasyRefreshController get refreshController => _refreshController;

  /// 当前页码
  int _currentPageNum = pageNumFirst;

  ViewStateRefreshListModel({super.viewState, bool isMore = true})
      : _refreshController = EasyRefreshController(
            controlFinishLoad: isMore, 
            controlFinishRefresh: true);

  /// 下拉刷新
  @override
  Future<List<T>> refresh({bool init = false}) async {
    try {
      _currentPageNum = pageNumFirst;
      var data = await loadData(pageNum: pageNumFirst);
      refreshController.finishRefresh();
      
      if (data.isEmpty) {
        refreshController.finishLoad(IndicatorResult.none);
        list.clear();
        setEmpty();
      } else {
        onCompleted(data);
        list.clear();
        list.addAll(data);
        
        // 小于分页数量时禁止上拉加载更多
        if (data.length < pageSize) {
          Future.delayed(const Duration(milliseconds: 100)).then((value) {
            refreshController.finishLoad(IndicatorResult.noMore);
          });
        }
        setIdle();
      }
      return data;
    } catch (e) {
      if (init) list.clear();
      refreshController.finishLoad(IndicatorResult.fail);
      setEmpty();
      return [];
    }
  }

  /// 上拉加载更多
  Future<List<T>> loadMore() async {
    try {
      var data = await loadData(pageNum: ++_currentPageNum);
      refreshController.finishRefresh();
      
      if (data.isEmpty) {
        _currentPageNum--;
        refreshController.finishLoad(IndicatorResult.noMore);
      } else {
        onCompleted(data);
        list.addAll(data);
        
        if (data.length < pageSize) {
          refreshController.finishLoad(IndicatorResult.noMore);
        } else {
          refreshController.finishLoad();
        }
        notifyListeners();
      }
      return data;
    } catch (e) {
      _currentPageNum--;
      refreshController.finishLoad(IndicatorResult.fail);
      return [];
    }
  }

  /// 抽象方法:加载数据
  @override
  Future<List<T>> loadData({int? pageNum});

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

使用指南

1. 基础ViewModel示例

创建一个继承ViewStateModel的ViewModel

class UserProfileViewModel extends ViewStateModel {
  UserInfo? _userInfo;
  UserInfo? get userInfo => _userInfo;

  Future<void> loadUserProfile(String userId) async {
    setBusy(); // 设置加载状态
    
    try {
      _userInfo = await userRepository.getUserProfile(userId);
      setIdle(); // 设置空闲状态
    } catch (e) {
      setError(); // 设置错误状态
    }
  }
}

2. 在UI中使用ProviderWidget

class UserProfilePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderWidget<UserProfileViewModel>(
      model: UserProfileViewModel(),
      onModelReady: (model) => model.loadUserProfile("123"),
      builder: (context, model, child) {
        if (model.isBusy) {
          return Center(child: CircularProgressIndicator());
        }
        
        if (model.isEmpty) {
          return Center(child: Text('暂无数据'));
        }
        
        if (model.isError) {
          return Center(child: Text('加载失败'));
        }
        
        return Column(
          children: [
            Text(model.userInfo?.name ?? ''),
            Text(model.userInfo?.email ?? ''),
          ],
        );
      },
    );
  }
}

3. 列表刷新示例

class NewsListViewModel extends ViewStateRefreshListModel<NewsItem> {
  final NewsRepository _repository = NewsRepository();

  @override
  Future<List<NewsItem>> loadData({int? pageNum}) async {
    return await _repository.getNewsList(
      page: pageNum ?? 1,
      pageSize: ViewStateRefreshListModel.pageSize,
    );
  }
}

// UI使用
class NewsListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderWidget<NewsListViewModel>(
      model: NewsListViewModel(),
      onModelReady: (model) => model.refresh(init: true),
      builder: (context, model, child) {
        return EasyRefresh(
          controller: model.refreshController,
          onRefresh: () => model.refresh(),
          onLoad: () => model.loadMore(),
          child: ListView.builder(
            itemCount: model.list.length,
            itemBuilder: (context, index) {
              final item = model.list[index];
              return ListTile(
                title: Text(item.title),
                subtitle: Text(item.summary),
              );
            },
          ),
        );
      },
    );
  }
}

4. 双Model管理示例

class DashboardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderWidget2<UserViewModel, NotificationViewModel>(
      model1: UserViewModel(),
      model2: NotificationViewModel(),
      onModelReady: (userModel, notificationModel) {
        userModel.loadUserInfo();
        notificationModel.loadNotifications();
      },
      builder: (context, userModel, notificationModel, child) {
        return Scaffold(
          body: Column(
            children: [
              // 用户信息区域
              if (userModel.isBusy)
                CircularProgressIndicator()
              else
                UserInfoWidget(user: userModel.currentUser),
              
              // 通知区域
              if (notificationModel.isBusy)
                CircularProgressIndicator()
              else
                NotificationListWidget(notifications: notificationModel.notifications),
            ],
          ),
        );
      },
    );
  }
}

依赖配置

pubspec.yaml 关键依赖

dependencies:
  flutter:
    sdk: flutter
    
  # 状态管理
  provider: ^6.0.0
  
  # 列表刷新
  easy_refresh: ^3.0.0
  
  # 日志记录
  # (自定义日志工具类)

dev_dependencies:
  flutter_test:
    sdk: flutter

最佳实践

1. ViewModel设计原则

  • 继承ViewStateModel获得基础状态管理能力
  • 将业务逻辑封装在ViewModel中保持View的简洁
  • 合理使用状态枚举,提供良好的用户体验反馈
  • 及时释放资源,避免内存泄漏

2. 错误处理策略

  • 使用ViewStateErrorType枚举区分不同错误类型
  • 在UI层根据错误类型提供相应的用户提示
  • 网络错误提供重试机制

3. 列表优化建议

  • 使用ViewStateRefreshListModel处理列表数据
  • 合理设置分页大小,平衡性能和用户体验
  • 实现适当的缓存策略减少不必要的网络请求

4. 内存管理

  • ProviderWidget默认开启autoDispose自动管理Model生命周期
  • 在Model的dispose方法中清理定时器、流订阅等资源
  • 避免在已销毁的Model上调用notifyListeners

扩展开发

1. 自定义状态类型

可以扩展ViewState枚举添加业务特定的状态

enum CustomViewState {
  idle,
  busy,
  empty,
  error,
  networkError,    // 自定义网络错误状态
  authRequired,    // 自定义需要认证状态
}

2. 自定义刷新组件

基于base_mvvm的基础组件可以创建适合特定业务场景的刷新组件。

3. 状态持久化

结合SharedPreferences等持久化方案实现ViewState的持久化存储。

问题排查

常见问题

  1. Model未正确释放: 检查ProviderWidget的autoDispose设置
  2. 状态更新无效: 确认notifyListeners调用时机
  3. 列表刷新异常: 检查EasyRefreshController的状态管理

调试技巧

  • 使用LogUtils记录关键状态变更
  • 通过ViewState枚举值判断当前页面状态
  • 利用Flutter Inspector查看Provider状态