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

Flutter使用Provider从Firebase获取文档时出现重复数据问题

问题:Firebase集合文档重复加载,列表出现无限重复条目

问题原因

代码出现无限重复条目的核心原因有两个:

  1. Getter触发重复异步请求:在ClassProvider的classes getter中,每次获取列表都会调用getClassesList()方法,而该方法只会向_classes列表追加数据,不会先清空原有内容。
  2. 循环刷新触发:getClassesList()最后调用了notifyListeners(),会触发Consumer重新构建页面,页面再次调用classProvider.classes getter,又会触发getClassesList(),形成无限循环,导致相同文档被反复添加到列表中。

解决方案

1. 重构Provider中的数据获取逻辑

  • 移除classes getter中的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 07:35:01