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

如何在Riverpod中获取首个API响应后调用第二个API?

解决方案:基于Riverpod实现API依赖调用与下拉切换更新

核心问题梳理

当前存在的问题点:

  • 初始化时硬编码了固定值,未利用API1返回的drawList首项
  • 使用.family修饰符时,下拉切换会创建新Provider实例,导致状态不连贯
  • StateNotifier初始化未等待API1的结果,可能出现初始值缺失的情况

步骤1:完善API1的FutureProvider

确保API1的FutureProvider正确返回drawList列表:

final drawListProvider = FutureProvider<List<String>>((ref) async {
  final apiService = ref.read(apiProvider);
  final response = await get(Uri.parse("${apiService.baseUrl}/draws/list"));
  
  if (response.statusCode != 200) {
    throw Exception("加载draw列表失败");
  }
  
  return List<String>.from(jsonDecode(response.body));
});

步骤2:修改StateNotifierProvider依赖API1结果

让StateNotifier在API1数据就绪后初始化,自动使用drawList的第一个元素作为初始请求参数:

final updatedItemListProvider = StateNotifierProvider<UpdatedDropDownItemList, AsyncValue<PreviousDrawResponse>>((ref) {
  final apiService = ref.read(apiProvider);
  // 监听drawListProvider,获取初始值
  final drawList = ref.watch(drawListProvider).valueOrNull;
  
  return UpdatedDropDownItemList(apiService, initialValue: drawList?.first);
});

class UpdatedDropDownItemList extends StateNotifier<AsyncValue<PreviousDrawResponse>> {
  UpdatedDropDownItemList(this.service, {String? initialValue}) : super(const AsyncLoading()) {
    // 初始值存在时立即发起请求
    if (initialValue != null) {
      _fetchDrawData(initialValue);
    }
  }

  final ApiService service;

  void _fetchDrawData(String newValue) async {
    debugPrint('正在请求数据: $newValue');
    state = const AsyncLoading();
    
    try {
      final result = await service.getDrawItemList(newValue);
      state = result;
    } catch (e, stackTrace) {
      state = AsyncError('加载失败: $e', stackTrace);
    }
  }

  // 对外暴露的切换方法,供下拉菜单调用
  void switchProgram(String newValue) => _fetchDrawData(newValue);
}

步骤3:修正ApiService的硬编码问题

移除静态字符串,直接使用传入参数,并对参数做URL编码避免特殊字符导致的请求错误:

Future<AsyncValue<PreviousDrawResponse>> getDrawItemList(String newValue) async {
  // URL编码参数,处理空格、特殊字符等情况
  final encodedValue = Uri.encodeComponent(newValue);
  final url = "$baseUrl/draws?filter[immigration_program]=$encodedValue";
  
  final response = await get(Uri.parse(url));

  if (response.statusCode == 200) {
    debugPrint("getDrawItemList响应: ${jsonDecode(response.body)}");
    final data = PreviousDrawResponse.fromJson(jsonDecode(response.body));
    return AsyncData(data);
  } else {
    debugPrint("getDrawItemList错误: ${response.statusCode}");
    return AsyncError('加载数据失败!', StackTrace.current);
  }
}

步骤4:UI层实现下拉切换与数据展示

在UI中监听两个Provider,完成下拉菜单交互和过滤数据展示:

@override
Widget build(BuildContext context, WidgetRef ref) {
  final drawListState = ref.watch(drawListProvider);
  final filteredDataState = ref.watch(updatedItemListProvider);

  return drawListState.when(
    loading: () => const Center(child: CircularProgressIndicator()),
    error: (err, stack) => Center(child: Text('加载列表失败: $err')),
    data: (drawList) {
      return Column(
        children: [
          DropdownButton<String>(
            // 默认选中当前过滤项,无数据时选列表首项
            value: filteredDataState.value?.selectedProgram ?? drawList.first,
            items: drawList.map((program) {
              return DropdownMenuItem(
                value: program,
                child: Text(program),
              );
            }).toList(),
            onChanged: (newValue) {
              if (newValue != null) {
                ref.read(updatedItemListProvider.notifier).switchProgram(newValue);
              }
            },
          ),
          Expanded(
            child: filteredDataState.when(
              loading: () => const Center(child: CircularProgressIndicator()),
              error: (err, stack) => Center(child: Text('加载数据失败: $err')),
              data: (data) {
                // 示例:用ListView展示过滤后的draw数据
                return ListView.builder(
                  itemCount: data.draws.length,
                  itemBuilder: (context, index) {
                    final draw = data.draws[index];
                    return ListTile(title: Text(draw.name));
                  },
                );
              },
            ),
          ),
        ],
      );
    },
  );
}

为什么.family不适合这个场景

.family修饰符会根据传入参数生成独立的Provider实例,下拉切换选项时,参数变化会创建全新的StateNotifier实例,原有加载状态、数据会被完全重置,无法保持状态连贯性。而单个StateNotifierProvider维护一个实例,通过方法调用更新状态,更适配这种需要动态切换参数且保留状态的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:40:34