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

