Flutter 级联下拉框初始化同时调用导致第二个下拉加载失效问题
问题核心原因
两个逻辑漏写导致联动失效:
- 初始化构建时没有对第二级下拉的接口请求加前置判断,页面首次渲染就带着无效的空课程ID请求班级接口,自然返回空列表
- 第一级课程下拉的选中回调里,没有主动触发第二级班级列表的重新拉取和状态更新,选完课程后第二级组件根本感知不到依赖的课程ID变了,不会刷新数据。
修复方案
按下面的逻辑改,就能实现正常联动:
- 状态初始化时,只在
initState里拉取第一级课程列表,不要在build阶段触发任何下拉数据请求——build方法会在键盘弹出、父组件重建、主题变化等多种场景反复执行,写在这里会产生大量无效重复请求。 - 给第二级班级接口加严格的前置拦截:只有拿到合法非空的课程ID时才发请求,未选课程时直接将班级列表置空、重置已选班级值,同时把第二级下拉设为禁用状态,避免用户误操作。
- 第一级课程的选中回调里,必须同步做三个操作:更新已选课程ID、清空旧的班级列表和已选班级值、主动调用班级列表请求方法拉取新课程对应的数据,之后通过
setState触发UI更新。
可直接复用的参考代码
// 省略无关的导入、主题配置代码 class LinkedCourseClassDropdown extends StatefulWidget { const LinkedCourseClassDropdown({super.key}); @override State<LinkedCourseClassDropdown> createState() => _LinkedCourseClassDropdownState(); } class _LinkedCourseClassDropdownState extends State<LinkedCourseClassDropdown> { // 第一级选中值:课程ID,初始为null代表未选择 int? selectedCourseId; // 第二级选中值:班级ID int? selectedClassId; // 数据源 List<Course> courseList = []; List<ClassInfo> classList = []; // 加载状态标记,可选,优化交互 bool isCourseLoading = false; bool isClassLoading = false; @override void initState() { super.initState(); // 初始化仅拉取第一级课程数据 _loadCourseList(); } // 拉取课程列表 Future<void> _loadCourseList() async { setState(() => isCourseLoading = true); try { // 替换成你自己项目的课程接口调用 final res = await CourseApi.getList(); courseList = res.data ?? []; } catch (e) { // 自行加错误提示 } finally { if (mounted) setState(() => isCourseLoading = false); } } // 拉取对应课程下的班级列表 Future<void> _loadClassList(int courseId) async { // 切换课程先清空旧数据,避免出现旧数据残留、选中值非法的报错 setState(() { classList = []; selectedClassId = null; isClassLoading = true; }); try { // 把选中的课程ID传给班级接口 final res = await ClassApi.getList(courseId: courseId); classList = res.data ?? []; } catch (e) { // 自行加错误提示 } finally { if (mounted) setState(() => isClassLoading = false); } } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一级:课程下拉 const Text("所属课程"), isCourseLoading ? const Padding(padding: EdgeInsets.all(8), child: CircularProgressIndicator(strokeWidth: 2,)) : DropdownButton<int>( value: selectedCourseId, isExpanded: true, hint: const Text("请选择课程"), items: courseList.map((course) => DropdownMenuItem( value: course.id, child: Text(course.courseName), )).toList(), onChanged: (newCourseId) { if (newCourseId == null) return; setState(() => selectedCourseId = newCourseId); // 选完课程立刻触发班级数据拉取,不要等build自动触发 _loadClassList(newCourseId); }, ), const SizedBox(height: 16), // 第二级:班级下拉 const Text("所属班级"), isClassLoading ? const Padding(padding: EdgeInsets.all(8), child: CircularProgressIndicator(strokeWidth: 2,)) : DropdownButton<int>( value: selectedClassId, isExpanded: true, // 未选课程时提示用户先选上一级 hint: Text(selectedCourseId == null ? "请先选择课程" : "请选择班级"), items: classList.map((cls) => DropdownMenuItem( value: cls.id, child: Text(cls.className), )).toList(), // 未选课程/班级加载中时直接禁用下拉,交互更清晰 onChanged: selectedCourseId == null ? null : (newClassId) { setState(() => selectedClassId = newClassId); }, ), ], ); } } // 对应实体类,按你项目的实际接口字段改就行 class Course { final int id; final String courseName; Course({required this.id, required this.courseName}); } class ClassInfo { final int id; final String className; ClassInfo({required this.id, required this.className}); }
避坑提醒
- 切换上一级选项时一定要清空下一级的已选值,否则会出现「选中值不在当前选项列表中」的框架报错,毕竟不同课程下的班级ID不可能完全重合。
- 不要在
didChangeDependencies或者build方法里直接调用下拉数据请求,除非加了精准的变量变化判断,否则会产生大量重复请求。 - 如果弹窗里用的是第三方下拉组件,注意看组件本身有没有提供
onOpen、rebuild这类回调,不要依赖组件自身的初始化逻辑触发请求,主动在课程选中后调用刷新方法最稳妥。
内容的提问来源于stack exchange,提问作者Mymoon Jaleel
相关产品推荐
相关产品推荐

