如何使用 StreamBuilder 从 Firebase 检索嵌套数据并将其全部显示到 ListView

How to retrieve nested data from Firebase using StreamBuilder and display it all to ListView

我正在尝试从 Firebase 检索嵌套数组数据并将其显示在 ListView Builder 上,但我在下面遇到了错误。

Exception has occurred.
StateError (Bad state: field does not exist within the DocumentSnapshotPlatform)

请指导我。我需要帮助。

我用来将嵌套数据存储到主集合的模型

class SPMModel {
  String? subjectName;
  String? subjectGrade;

  SPMModel(this.subjectName, this.subjectGrade);

  Map<String, dynamic> toMap() => {
    "subjectName": subjectName,
    "subjectGrade": subjectGrade,
  };
}

主要藏品的型号

class UserProfileModel {
  String? uid;
  String? email;
  String? fullName;
  String? nric;
  String? age;
  String? gender;
  String? ethnicity;
  String? religion;
  String? address;
  String? state;
  String? country;
  String? phone;
  String? parentName;
  String? parentPhone;
  List<dynamic>? spmResult = []; //I'm using this variable to store the SPMModel data

  UserProfileModel({
    this.uid,
    this.email,
    this.fullName,
    this.nric,
    this.age,
    this.gender,
    this.ethnicity,
    this.religion,
    this.address,
    this.state,
    this.country,
    this.phone,
    this.parentName,
    this.parentPhone,
    this.spmResult
  });

  //receive data from database
  factory UserProfileModel.fromMap(map) {
    return UserProfileModel(
      uid: map['uid'],
      email: map['email'],
      fullName: map['fullName'],
      nric: map['nric'],
      age: map['age'],
      gender: map['gender'],
      ethnicity: map['ethnicity'],
      religion: map['religion'],
      address: map['address'],
      state: map['state'],
      country: map['country'],
      phone: map['phone'],
      parentName: map['parentName'],
      parentPhone: map['parentPhone'],
      spmResult: map['spmResult']
    );
  }

  //send data to database
  Map<String, dynamic> toMap() {
    return {
      'uid': uid,
      'email': email,
      'fullName': fullName,
      'nric': nric,
      'age': age,
      'gender': gender,
      'ethnicity': ethnicity,
      'religion': religion,
      'address': address,
      'state': state,
      'country': country,
      'phone': phone,
      'parentName': parentName,
      'parentPhone': parentPhone,
      'spmResult': spmResult
    };
  }
}

我的 StreamBuilder

StreamBuilder<QuerySnapshot>(
  stream: FirebaseFirestore.instance.collection("users").where('uid', isEqualTo: FirebaseAuth.instance.currentUser!.uid).snapshots(),
  builder: (context, snapshot){
    if(!snapshot.hasData){
      return const Center(child: Text('No data found'),);
    }
    return ListView.builder(
      shrinkWrap: true,
      itemCount: snapshot.data!.docs.length,
      itemBuilder: (context, index){
        return Column(
          children: <Widget>[
            Text('id : ${snapshot.data!.docs[index].toString()}'),
            Text('subject: ${snapshot.data!.docs[index]['subjectName']}'),
            Text('grade: ${snapshot.data!.docs[index]['subjectGrade']}'),
          ],
        );
      }
    );
  }
)

这是 Firebase 数据的样子

我自己的问题已经解决了

这是解决方案

StreamBuilder(
  stream: FirebaseFirestore.instance.collection("users").doc(user!.uid).
snapshots(),
  builder: (context, AsyncSnapshot snapshot){
    if(!snapshot.hasData){
      return const Text('No data found, yet');
    }
    return SingleChildScrollView(
      child: DataTable(
        border: TableBorder.all(),
        columnSpacing: 0,
        horizontalMargin: 8, 
        columns: <DataColumn>[
          DataColumn(
            label: SizedBox(
              child: const Text('Subjects', textAlign: TextAlign.center),
              width: MediaQuery.of(context).size.width * .60
            )
          ),
          DataColumn(
            label: SizedBox(
              child: const Text('Grades', textAlign: TextAlign.center),
              width: MediaQuery.of(context).size.width * .15
            )
          ),
          DataColumn(
            label: SizedBox(
              child: const Text('Actions', textAlign: TextAlign.center),
              width: MediaQuery.of(context).size.width * .15
            )
          ),
        ],
        rows: _buildList(context, snapshot.data['spmResult'])
      ),
    );
  },
)

我得到快照后,通过创建这些方法使用DataTable来显示数组

List<DataRow> _buildList(BuildContext context, List<dynamic> snapshot) {
  return snapshot.map((data) => _buildListItem(context, data)).toList();
}
DataRow _buildListItem(BuildContext context, data) {
  final record = SPMModel.fromJson(data);
  return DataRow(
    cells: <DataCell>[
      DataCell(Text(record.subjectName.toString())),
      DataCell(Center(child: Text(record.subjectGrade.toString()))),
      const DataCell(Center(child: Icon(Icons.delete)))
    ]
  );
}

对于我的模型,这是更新版本

用户Collection型号

class UserProfileModel {
  List<SPMModel>? spmResult;

  UserProfileModel({
    this.spmResult,
  });

  //receive data from database
  factory UserProfileModel.fromMap(map) {
    return UserProfileModel(
      spmResult: SPMModel.fromJsonArray(map['spmResult'])
    );
  }

  //send data to database
  Map<String, dynamic> toMap() {
    return {
      'spmResult': spmResult
    };
  }
}

数组模型

class SPMModel {
  String? subjectName;
  String? subjectGrade;

  SPMModel({this.subjectName, this.subjectGrade});

  Map<String, dynamic> toMap() => {
    "subjectName": subjectName,
    "subjectGrade": subjectGrade,
  };
  
  factory SPMModel.fromJson(Map<String, dynamic> json){
    return SPMModel(
      subjectName: json['subjectName'],
      subjectGrade: json['subjectGrade']
    );
  }

  static List<SPMModel> fromJsonArray(List<dynamic> jsonArray){
    List<SPMModel> spmModelFromJson = [];

    for (var item in jsonArray) {
      spmModelFromJson.add(SPMModel.fromJson(item));
    }

    return spmModelFromJson;
  }
}

用于存储数组到主collection

await FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.update({
  "spmResult": FieldValue.arrayUnion([SPMModel(subjectName: subject, subjectGrad
grade).toMap()])
}).then((value) {
  ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text
('Successfully Added')));
})                        
.catchError((onError){
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${onError.
message}')));
});

这是模拟器的输出