页面跳转后Bloc状态更新无法触发Widget响应问题排查
Flutter Bloc状态接收异常问题排查与解决
问题背景
我有两个页面:页面1含按钮,点击后跳转至页面2;页面2需响应Bloc发射的状态展示数据。此前fixedExpenseTotal、variableExpenseTotal等变量显示正常,但新增FetchedBalanceBoughtForwardState状态后,这些变量变为空字符串。观察发现:
- 页面2初始加载时无法响应状态,手动让Bloc重发状态则正常
- 添加print语句后,页面2初始仅接收
FetchedBalanceBoughtForwardState,未接收FetchedExpendituresForCertainMonthState - 怀疑状态在Widget加载前已发射,纠结是否要将
balance并入FetchedExpendituresForCertainMonthState(虽希望保持状态/事件解耦)
相关代码片段
Bloc核心逻辑
on<FetchForMonthExpenditureDefinitions>((event, emit) async { //...业务逻辑 emit(FetchedExpendituresForCertainMonthState( expendituresWithinTheMonth, monthToTraverse)); // 计算上月结转余额 add(CalculateBalanceBoughtForwardEvent(expenditures, from)); }); on<CalculateBalanceBoughtForwardEvent>((event, emit) async { double balance = calculator.calculateBalanceBeingBoughtForward(event.expenditureDefinitions, event.dateOfBalanceBoughtForward); print('Sending the balance bought forward as $balance'); emit(FetchedBalanceBoughtForwardState(balance)); });
页面1按钮跳转逻辑
TextButton( child: const Text('Finish!'), onPressed: () async { context.read<FinanceBloc>().add( FetchForMonthExpenditureDefinitions( DateTime.now())); Navigator.of(context).push(MaterialPageRoute( builder: (_) => BlocProvider.value( value: BlocProvider.of<FinanceBloc>(context), child: MyApp(), // 注:此处MyApp应为页面2组件,建议命名为Page2等更清晰的名称 ), )); }, )
页面2状态响应Widget
return Container( width: MediaQuery.sizeOf(context).width, child: Container( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.0), color: Colors.grey, ), child: BlocBuilder<FinanceBloc, FinanceState>( buildWhen: (previous, current) => previous != current && current is FetchedBalanceBoughtForwardState, builder: (context, balanceState) { double balanceBeingBoughtForward = balanceState is FetchedBalanceBoughtForwardState ? balanceState.balanceBoughtForward : 0.0; return BlocBuilder<FinanceBloc, FinanceState>( buildWhen: (previous, current) => previous != current && current is FetchedExpendituresForCertainMonthState, builder: (context, expendituresState) { String fixedIncomeTotal = ""; String fixedExpenseTotal = ""; String variableExpenseTtoal = ""; String variableIncomeTotla = ""; if (expendituresState is FetchedExpendituresForCertainMonthState) { expenditures = expendituresState.expenditures.values .expand((list) => list) .toList(); currentMonth = expendituresState.updatedDate; fixedIncomeTotal = getFixedIncome(expenditures).toString(); fixedExpenseTotal = getFixedExpense(expenditures).toString(); variableExpenseTtoal = getVariableExpense(expenditures).toString(); variableIncomeTotla = getVariableIncome(expenditures).toString(); } return Column(children: [ Text("Overview of expenditures for this month", style: const TextStyle(fontSize: 24)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Total fixed income: £$fixedIncomeTotal"), Text("Total fixed expense: £$fixedExpenseTotal") ] ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Total variable income: £$variableIncomeTotla"), Text("Total variable expense: £$variableExpenseTtoal") ] ), Text("Balance bought forward: £$balanceBeingBoughtForward"), Text("Click here for more intel!"), ]); }, ); }, ), ), );
问题原因
- 跳转时机不匹配:按钮点击时先触发Bloc事件,随即跳转页面。此时
FetchedExpendituresForCertainMonthState可能在页面2的BlocBuilder完成订阅前就已发射,而BlocBuilder默认仅监听订阅后的状态更新,导致初始状态未被捕获。 - 嵌套BlocBuilder的逻辑缺陷:外层BlocBuilder仅监听
FetchedBalanceBoughtForwardState,内层仅监听FetchedExpendituresForCertainMonthState。当页面2加载时,Bloc的当前状态已变为FetchedBalanceBoughtForwardState,内层BlocBuilder的buildWhen条件(状态为FetchedExpendituresForCertainMonthState)不满足,因此不会触发构建,导致变量为空。
解决方案
方案1:等待状态发射完成后再跳转
修改页面1的按钮逻辑,通过监听Bloc流等待目标状态发射后再执行跳转,确保页面2加载时Bloc已处于预期状态:
TextButton( child: const Text('Finish!'), onPressed: () async { final bloc = context.read<FinanceBloc>(); bloc.add(FetchForMonthExpenditureDefinitions(DateTime.now())); // 等待支出数据状态发射完成 await bloc.stream.firstWhere( (state) => state is FetchedExpendituresForCertainMonthState ); Navigator.of(context).push(MaterialPageRoute( builder: (_) => BlocProvider.value( value: bloc, child: MyApp(), ), )); }, )
方案2:合并BlocBuilder监听逻辑
将两个嵌套的BlocBuilder合并为一个,同时监听两种状态,并在builder中直接获取Bloc的当前状态(避免仅依赖builder参数):
return Container( width: MediaQuery.sizeOf(context).width, child: Container( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.0), color: Colors.grey, ), child: BlocBuilder<FinanceBloc, FinanceState>( buildWhen: (previous, current) { // 监听两种目标状态 return current is FetchedExpendituresForCertainMonthState || current is FetchedBalanceBoughtForwardState; }, builder: (context, state) { final bloc = context.read<FinanceBloc>(); double balanceBeingBoughtForward = 0.0; String fixedIncomeTotal = ""; String fixedExpenseTotal = ""; String variableExpenseTotal = ""; String variableIncomeTotal = ""; // 处理余额状态 if (bloc.state is FetchedBalanceBoughtForwardState) { balanceBeingBoughtForward = (bloc.state as FetchedBalanceBoughtForwardState).balanceBoughtForward; } // 处理支出数据状态 if (bloc.state is FetchedExpendituresForCertainMonthState) { final expState = bloc.state as FetchedExpendituresForCertainMonthState; final expenditures = expState.expenditures.values .expand((list) => list) .toList(); currentMonth = expState.updatedDate; fixedIncomeTotal = getFixedIncome(expenditures).toString(); fixedExpenseTotal = getFixedExpense(expenditures).toString(); variableExpenseTotal = getVariableExpense(expenditures).toString(); variableIncomeTotal = getVariableIncome(expenditures).toString(); } return Column(children: [ Text("Overview of expenditures for this month", style: const TextStyle(fontSize: 24)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Total fixed income: £$fixedIncomeTotal"), Text("Total fixed expense: £$fixedExpenseTotal") ] ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Total variable income: £$variableIncomeTotal"), Text("Total variable expense: £$variableExpenseTotal") ] ), Text("Balance bought forward: £$balanceBeingBoughtForward"), Text("Click here for more intel!"), ]); }, ), ), );
可选优化方向
- 合并状态(简化逻辑):如果
支出数据和结转余额是同一操作的关联结果,可以合并为一个状态(如FetchedMonthOverviewState),包含支出列表、月份、结转余额等字段,减少状态数量,避免状态顺序问题。 - 保持解耦(推荐方案1):若希望维持状态/事件的解耦,方案1(等待状态再跳转)是更合适的选择,既保证逻辑清晰,又能解决初始状态不响应的问题。
内容的提问来源于stack exchange,提问作者Tloz
相关产品推荐
相关产品推荐

