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
相关产品推荐
相关产品推荐

