如何使用Bloc在单个UI中展示两种状态的数据
用Bloc实现依赖API的顺序请求流程
核心思路
通过Bloc的状态流转,先完成第一个API请求,拿到所需ID后自动触发第二个API请求,最终将两个接口的数据整合到状态中供UI展示。
1. 定义数据模型
先为两个API的返回数据创建对应模型:
// 第一个API返回模型 class FirstApiResponse { final int id; final String name; FirstApiResponse({required this.id, required this.name}); // 从JSON解析的工厂方法 factory FirstApiResponse.fromJson(Map<String, dynamic> json) { return FirstApiResponse( id: json['id'], name: json['name'], ); } } // 第二个API返回模型 class SecondApiResponse { final int relatedId; final String detail; SecondApiResponse({required this.relatedId, required this.detail}); factory SecondApiResponse.fromJson(Map<String, dynamic> json) { return SecondApiResponse( relatedId: json['related_id'], detail: json['detail'], ); } }
2. 定义Bloc事件
只需要一个触发整体流程的事件即可:
abstract class DataEvent {} class FetchCombinedDataEvent extends DataEvent {}
3. 定义Bloc状态
区分不同阶段的状态,方便UI做对应渲染:
abstract class DataState {} class DataInitial extends DataState {} class DataLoading extends DataState {} class FirstDataLoaded extends DataState { final FirstApiResponse firstData; FirstDataLoaded(this.firstData); } class BothDataLoaded extends DataState { final FirstApiResponse firstData; final SecondApiResponse secondData; BothDataLoaded({required this.firstData, required this.secondData}); } class DataError extends DataState { final String message; DataError(this.message); }
4. 实现Bloc业务逻辑
在Bloc中处理API调用的顺序和状态流转:
class DataBloc extends Bloc<DataEvent, DataState> { final ApiService apiService; // 统一的API服务类 DataBloc(this.apiService) : super(DataInitial()) { on<FetchCombinedDataEvent>(_fetchCombinedData); } Future<void> _fetchCombinedData( FetchCombinedDataEvent event, Emitter<DataState> emit, ) async { emit(DataLoading()); try { // 第一步:请求第一个API final firstData = await apiService.getFirstData(); emit(FirstDataLoaded(firstData)); // 第二步:用第一个API返回的ID请求第二个API final secondData = await apiService.getSecondData(firstData.id); emit(BothDataLoaded(firstData: firstData, secondData: secondData)); } catch (e) { emit(DataError(e.toString())); } } } // 示例API服务类(实际项目中按需封装) class ApiService { Future<FirstApiResponse> getFirstData() async { // 模拟API请求,实际替换为真实HTTP调用 await Future.delayed(const Duration(seconds: 1)); return FirstApiResponse(id: 1, name: "First Item"); } Future<SecondApiResponse> getSecondData(int id) async { await Future.delayed(const Duration(seconds: 1)); return SecondApiResponse(relatedId: id, detail: "Detail for ID $id"); } }
5. UI层集成Bloc
通过BlocBuilder监听状态变化,渲染对应UI:
class CombinedDataScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Combined Data")), body: BlocBuilder<DataBloc, DataState>( builder: (context, state) { if (state is DataInitial) { // 初始状态:触发数据请求 context.read<DataBloc>().add(FetchCombinedDataEvent()); return const Center(child: Text("Tap to load data")); } else if (state is DataLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is FirstDataLoaded) { // 第一个数据加载完成,等待第二个数据 return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("First Data: ${state.firstData.name}"), const SizedBox(height: 16), const CircularProgressIndicator(), ], ), ); } else if (state is BothDataLoaded) { // 两个数据都加载完成,展示内容 return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("First API Data:", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), Text("ID: ${state.firstData.id}, Name: ${state.firstData.name}"), const SizedBox(height: 24), Text("Second API Data:", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), Text("Related ID: ${state.secondData.relatedId}, Detail: ${state.secondData.detail}"), ], ), ); } else if (state is DataError) { return Center(child: Text("Error: ${state.message}")); } return const SizedBox(); }, ), ); } }
关键注意点
- 状态细分:把
FirstDataLoaded作为中间状态,能让UI在第二个API加载时展示部分数据,提升用户体验。 - 异常捕获:在Bloc的API调用流程中统一捕获异常,避免崩溃并反馈错误状态。
- 依赖注入:通过构造函数传入
ApiService,方便后续单元测试时替换为模拟实现。
内容的提问来源于stack exchange,提问作者Abhay kumar bhumihar
相关产品推荐
相关产品推荐

