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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:46:14