Flutter实时数据库联动下拉框切换时报错问题求助
Flutter Firebase实时数据库联动下拉框切换异常问题
问题描述
使用Flutter结合Firebase实时数据库实现三级联动下拉框(Exam → Semester → Course),首次加载时能正常显示Exam选项(spring、fall)及对应层级的Semester、Course,但切换Exam下拉框选项时出现错误。
用户提供的原始代码
import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; class homepg extends StatelessWidget { const homepg({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("home"), ), body: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { var ref = FirebaseDatabase.instance.ref(); var _selectedexam; var _selectedsemester; var _selectedcourse; @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(15), child: Center( child: Column( children: <Widget>[ Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Exam ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), ), const SizedBox( width: 40, ), Container( width: 400, child: FutureBuilder( future: ref.child("/Exam").get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()))) .toList(), value: _selectedexam, onChanged: (String? newValue) { setState(() { _selectedexam = newValue!; print(_selectedexam); }); }, ); }), ), ], ), const SizedBox( height: 100, ), Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Semester", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), )), const SizedBox( width: 20, ), Container( width: 400, child: FutureBuilder( future: ref.child("/Exam/$_selectedexam").get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()))) .toList(), value: _selectedsemester, onChanged: (String? newValue) { setState(() { _selectedsemester = newValue!; print(_selectedsemester); }); }, ); }), ), ], ), const SizedBox( height: 100, ), Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Course", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), )), const SizedBox( width: 40, ), Container( width: 400, child: FutureBuilder( future: ref .child("/Exam/$_selectedexam/$_selectedsemester") .get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()))) .toList(), value: _selectedcourse, onChanged: (String? newValue) { setState(() { _selectedcourse = newValue!; print(_selectedcourse); }); }, ); }), ), ], ), ], ), ), ); } }
错误原因分析
- 下级选中值未重置:切换Exam选项时,仅更新了
_selectedexam,但_selectedsemester和_selectedcourse仍保留旧值,这些旧值在新的Exam对应的子列表中不存在,会导致DropdownButton抛出"Value not in items"异常。 - 空值路径请求错误:初始状态下
_selectedexam为null,此时Semester的数据库请求路径为/Exam/null,会触发无效请求报错。 - FutureBuilder未主动重建:Semester和Course的FutureBuilder依赖上级选中值,但上级值变化时,Future对象未更新,导致请求不会重新触发,无法加载新的子列表数据。
修复方案
1. 切换上级选项时重置下级选中值
在Exam的onChanged回调中,清空下级选中状态,避免旧值不匹配新数据:
onChanged: (String? newValue) { setState(() { _selectedexam = newValue!; _selectedsemester = null; // 重置学期选中值 _selectedcourse = null; // 重置课程选中值 print(_selectedexam); }); },
2. 增加空值校验,避免无效请求
在Semester和Course的FutureBuilder前添加校验,提示用户先完成上级选择:
// Semester下拉框部分 child: _selectedexam == null ? const Text("请先选择Exam") : FutureBuilder(...), // Course下拉框部分 child: (_selectedexam == null || _selectedsemester == null) ? const Text("请先选择Exam和Semester") : FutureBuilder(...),
3. 完善类型声明与默认提示
为选中变量添加明确的类型声明,并给DropdownButton添加hint属性提示用户选择:
String? _selectedexam; String? _selectedsemester; String? _selectedcourse; // 示例:Exam下拉框添加hint DropdownButton<String>( hint: const Text("选择Exam"), // ...其他属性 )
完整修复后的代码
import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; class homepg extends StatelessWidget { const homepg({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("home"), ), body: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final ref = FirebaseDatabase.instance.ref(); String? _selectedexam; String? _selectedsemester; String? _selectedcourse; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.all(15), child: Center( child: Column( children: <Widget>[ Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Exam ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), ), const SizedBox(width: 40), SizedBox( width: 400, child: FutureBuilder( future: ref.child("/Exam").get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, hint: const Text("选择Exam"), items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()), )) .toList(), value: _selectedexam, onChanged: (String? newValue) { setState(() { _selectedexam = newValue!; _selectedsemester = null; _selectedcourse = null; print(_selectedexam); }); }, ); }, ), ), ], ), const SizedBox(height: 100), Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Semester", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), ), const SizedBox(width: 20), SizedBox( width: 400, child: _selectedexam == null ? const Text("请先选择Exam") : FutureBuilder( future: ref.child("/Exam/$_selectedexam").get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, hint: const Text("选择Semester"), items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()), )) .toList(), value: _selectedsemester, onChanged: (String? newValue) { setState(() { _selectedsemester = newValue!; _selectedcourse = null; print(_selectedsemester); }); }, ); }, ), ), ], ), const SizedBox(height: 100), Row( children: [ const Align( alignment: Alignment.centerLeft, child: Text( "Course", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), ), const SizedBox(width: 40), SizedBox( width: 400, child: (_selectedexam == null || _selectedsemester == null) ? const Text("请先选择Exam和Semester") : FutureBuilder( future: ref.child("/Exam/$_selectedexam/$_selectedsemester").get(), builder: (context, AsyncSnapshot<DataSnapshot> asyncSnapshot) { if (asyncSnapshot.hasError) return Text("Error: ${asyncSnapshot.error}"); if (!asyncSnapshot.hasData) return const CircularProgressIndicator(); return DropdownButton<String>( isExpanded: true, hint: const Text("选择Course"), items: asyncSnapshot.data!.children .map((snap) => DropdownMenuItem( value: snap.key, child: Text(snap.key.toString()), )) .toList(), value: _selectedcourse, onChanged: (String? newValue) { setState(() { _selectedcourse = newValue!; print(_selectedcourse); }); }, ); }, ), ), ], ), ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Falak Khalid
相关产品推荐
相关产品推荐

