0

I am trying to retrieve nested array data from Firebase and display it on ListView Builder but I got an error of this below.

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

Please guide me. I need help.

The model that I used to store the nested data to main collection

class SPMModel {
  String? subjectName;
  String? subjectGrade;

  SPMModel(this.subjectName, this.subjectGrade);

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

The model of the main collection

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
    };
  }
}

My 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']}'),
          ],
        );
      }
    );
  }
)

Here is the firebase data looks like FirestoreDB

1 Answer 1

1

I have solved my own problem.

Here is the solution

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'])
      ),
    );
  },
)

After I get the snapshot, I used DataTable to display the array by creating these methods

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)))
    ]
  );
}

For my models, here is the updated version

User Collection model

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
    };
  }
}

Array model

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;
  }
}

For storing the array to the main 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}')));
});

Here is the output from emulator Output

Sign up to request clarification or add additional context in comments.

1 Comment

You can accept your answer so that the community knows that there is an answer that has helped the OP.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.