Flutter使用Provider从Firebase获取文档时出现重复数据问题
问题:Firebase集合文档重复加载,列表出现无限重复条目
问题原因
代码出现无限重复条目的核心原因有两个:
- Getter触发重复异步请求:在
ClassProvider的classesgetter中,每次获取列表都会调用getClassesList()方法,而该方法只会向_classes列表追加数据,不会先清空原有内容。 - 循环刷新触发:
getClassesList()最后调用了notifyListeners(),会触发Consumer重新构建页面,页面再次调用classProvider.classesgetter,又会触发getClassesList(),形成无限循环,导致相同文档被反复添加到列表中。
解决方案
1. 重构Provider中的数据获取逻辑
- 移除
classesgetter中的getClassesList()调用,让getter仅负责返回数据副本,不触发异步操作。 - 修改
getClassesList()方法,在获取新数据前先清空_classes列表,避免重复追加。 - 让数据获取方法仅在需要时被调用(比如页面初始化时),而非每次获取列表时都执行。
2. 在页面初始化时触发数据加载
在展示列表的页面的initState中调用Provider的getClassesList(),确保数据仅加载一次。
修改后的代码
修改后的ClassProvider代码
class ClassProvider with ChangeNotifier { final CollectionReference classList = FirebaseFirestore.instance.collection("classes"); final List<Class> _classes = []; final List<String> _recordList = []; final List<Student> _studentList = []; // 获取班级列表方法:先清空再添加新数据 Future getClassesList() async { _classes.clear(); // 关键:先清空原有列表 final querySnapshot = await classList.get(); for (var element in querySnapshot.docs) { _classes.add(Class( name: element.get("className"), subject: element.get("subject"), instructor: element.get("instructor"))); } notifyListeners(); } // Getter仅返回数据副本,不再触发异步请求 List<Class> get classes { return [..._classes]; } List<String> get recordList { return [..._recordList]; } List<Student> get studentList{ return [..._studentList]; } Class findById(String className) { return _classes.firstWhere((element) => element.name == className); } Future getRecordList(String className) async { _recordList.clear(); final CollectionReference currentRecordList = classList.doc(className).collection('record'); final querySnapshot = await currentRecordList.get(); for (var element in querySnapshot.docs) { _recordList.add(element.id); } notifyListeners(); } Future getStudentList(String className, String recordDate) async{ _studentList.clear(); final currentRecordDate = classList.doc(className).collection('record').doc(recordDate); final data = await currentRecordDate.get(); Map<String, dynamic> values = data.data() as Map<String, bool>; values.forEach((key, value) { _studentList.add(Student(name: key, isPresent: value)); }); notifyListeners(); } }
修改后的页面代码(添加initState调用)
class YourClassListPage extends StatefulWidget { const YourClassListPage({super.key}); @override State<YourClassListPage> createState() => _YourClassListPageState(); } class _YourClassListPageState extends State<YourClassListPage> { @override void initState() { super.initState(); // 页面初始化时调用一次数据加载 Provider.of<ClassProvider>(context, listen: false).getClassesList(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Consumer<ClassProvider>( builder: (context, classProvider, _){ final classList = classProvider.classes; return Padding( padding: const EdgeInsets.only(top: 5.0, left: 20.0, right: 20.0), child: classList.isEmpty ? const Center(child: CircularProgressIndicator()) : GridView.builder( itemCount: classList.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 1, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 2.0, ), itemBuilder: ((context, i) => InkWell( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), color: Colors.teal), child: Center( child: Text( classList[i].name, style: const TextStyle(fontSize: 20), ), ), ), onTap: () { Navigator.pushNamed(context, Records.routeName, arguments: classList[i].name); }, ))), ); }, ), ), ); } }
模型代码(无需修改)
class Class { final String name; final String subject; final String instructor; Class({ required this.name, required this.subject, required this.instructor, }); } class Student{ final String name; final bool isPresent; Student({ required this.name, required this.isPresent }); }
额外优化建议
- 可以在
getClassesList()中添加错误处理,避免因网络问题导致加载失败无提示。 - 如果需要实时监听Firebase集合的变化,可以使用
snapshots()而非get(),这样数据会自动更新,无需手动调用加载方法:
页面中使用Stream<List<Class>> getClassesStream() { return classList.snapshots().map((snapshot) { return snapshot.docs.map((doc) => Class( name: doc.get("className"), subject: doc.get("subject"), instructor: doc.get("instructor"), )).toList(); }); }StreamBuilder替代Consumer来监听数据流,这样能自动处理实时更新,也不会出现重复加载的问题。
内容的提问来源于stack exchange,提问作者Himanshu Bansal
相关产品推荐
相关产品推荐

