You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中ChangeNotifier子类ViewModel调用UI触发API无法存值问题

问题描述

我实现了继承自ChangeNotifier的BaseViewModel基类,业务ViewModel均继承该基类。基类init方法中调用API时,能正常将返回值存入子类ViewModel的变量;但从UI传入参数调用API(如getWeeklyJobByRouteID)时,API虽正常返回数据,却无法将值存入子类的_jobsDetailModel变量。


相关代码

1. BaseViewModel 实现代码

abstract class BaseViewModel with ChangeNotifier {
  bool _isLoading = false;
  bool _isDisposed = false;
  bool _isInitializeDone = false;

  FutureOr<void> _initState;

  BaseViewModel() {
    _init();
  }

  FutureOr<void> init();

  void _init() async {
    this.isLoading = true;
    _initState = init();
    await _initState;
    this._isInitializeDone = true;
    this.isLoading = false;
  }

  void changeStatus() => isLoading = !isLoading;

  void reloadState() {
    if (!isLoading) notifyListeners();
  }

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

  // Getters
  FutureOr<void> get initState => _initState;

  bool get isLoading => _isLoading;

  bool get isDisposed => _isDisposed;

  bool get isInitialized => _isInitializeDone;

  // Setters
  set isLoading(bool value) {
    _isLoading = value;
    scheduleMicrotask(() {
      if (!_isDisposed) notifyListeners();
    });
  }
}

2. RoutesViewModel 子类代码

class RoutesViewModel extends BaseViewModel {
  RoutesViewModel({
    required this.routesUseCase,
  });

  @override
  FutureOr<void> init() async {
    await getWeeklyRoutesByDriverID();
    // await getWeeklyJobByRouteID(
    //   routeID: '34',
    //   driverID: '',
    //   memoryCache: 0,
    // );
  }

  List<RoutesDetailModel> _routesDetailModel = [];

  final RoutesUseCase routesUseCase;
  String errorMessage = '';
  List<JobDetailsModel> _jobDetailsModel = [];
  RoutesDetailModel _routesDetailsModel = RoutesDetailModel.empty();

  Future getWeeklyRoutesByDriverID() async {
    changeStatus();
    final response = await routesUseCase.getWeeklyRoutesByDriverID();
    return response.fold(
      (success) {
        _routesDetailModel = success;
        changeStatus();
        return Left(
          _routesDetailModel,
        );
      },
      (r) {
        changeStatus();
        return Right(
          errorMessage = r.message,
          // showToast(
          //   message: r.message,
          //   context: context,
          // ),
        );
      },
    );
  }

  FutureOr<void> getWeeklyJobByRouteID({
    required String routeID,
    required String driverID,
    required int memoryCache,
  }) async {
    changeStatus();
    reloadState();
    notifyListeners();
    final response = await routesUseCase.getWeeklyJobsByRouteID(
      routeID: routeID,
      driverID: driverID,
      memoryCache: memoryCache,
    );
    return response.fold(
      (success) {
        _jobDetailsModel = success;
        changeStatus();
        notifyListeners();
        reloadState();
        return Left(
          _jobDetailsModel,
        );
      },
      (r) {
        changeStatus();
        return Right(
          Failure(
            message: r.message,
            status: r.status,
          ),
        );
      },
    );
  }
  
  /// Getters
  int get routesCount => _routesDetailModel.length;

  int get jobsCount => _jobDetailsModel.length;

  String getData(int index) => _routesDetailModel[index].route.toString();

  List<RoutesDetailModel> getRoutesModel(int index) => [
        _routesDetailModel[index],
      ];
}

3. UI 调用代码

class _JobsScreenState extends State<JobsScreen> {
  @override
  void initState() {
    final provider = Provider.of<RoutesViewModel>(context, listen: false);
    Future.delayed(
      Duration(seconds: 3),
      () {
        provider.getWeeklyJobByRouteID(
          routeID: widget.routesDetailModel[0].routeId.toString(),
          driverID: '',
          memoryCache: 0,
        );
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(

解决方案

1. 修正getWeeklyJobByRouteID返回类型与冗余调用

当前方法声明为FutureOr<void>,但内部返回Left/Right导致异步流程异常,同时存在冗余的状态通知调用。调整如下:

// 修改方法签名为与其他API方法一致的返回类型
Future<Either<List<JobDetailsModel>, Failure>> getWeeklyJobByRouteID({
  required String routeID,
  required String driverID,
  required int memoryCache,
}) async {
  changeStatus();
  // 移除冗余的reloadState()和notifyListeners(),changeStatus已触发通知
  final response = await routesUseCase.getWeeklyJobsByRouteID(
    routeID: routeID,
    driverID: driverID,
    memoryCache: memoryCache,
  );
  return response.fold(
    (success) {
      // 添加打印确认赋值是否成功
      print('API返回数据长度: ${success.length}');
      _jobDetailsModel = success;
      print('赋值后_jobsDetailModel长度: ${_jobDetailsModel.length}');
      changeStatus();
      return Left(success);
    },
    (r) {
      changeStatus();
      return Right(
        Failure(
          message: r.message,
          status: r.status,
        ),
      );
    },
  );
}

2. 确保UI正确监听ViewModel变化

在build方法中通过Consumer或Provider.of(带listen: true)监听jobsCount,确保变量更新时UI自动重建:

@override
Widget build(BuildContext context) {
  return SafeArea(
    child: Scaffold(
      body: Consumer<RoutesViewModel>(
        builder: (context, viewModel, child) {
          return Text('Jobs数量: ${viewModel.jobsCount}');
        },
      ),
    ),
  );
}

3. 验证ViewModel实例一致性

打印ViewModel的hashCode确认UI调用和监听的是同一个实例:

  • 在initState中打印:print('调用API的实例hash: ${provider.hashCode}')
  • 在Consumer的builder中打印:print('监听的实例hash: ${viewModel.hashCode}')
    若hashCode不一致,需检查Provider的注册方式,确保作用域正确。

内容的提问来源于stack exchange,提问作者Usama Tahir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:17:33