I'm developing an app for my school project and I stuck in an issue and I'm not able to handle it. I’m trying to get midTerm
, finalTerm
and lectureGradeId
values from my Firebase Cloud Firestore. Except that values, I'm able to get every value from Firestore.
To make it clear, I'm putting my codes and my Firestore structure below:
Students.swift
import Foundation
import FirebaseFirestore
struct Grades: Codable, Hashable {
var lectureGradeId: String
var midTerm: String
var endTerm: String
}
struct Students: Identifiable, Codable {
var id: String
var email: String
var studentName: String
var studentSurname: String
var studentTerm: String
var studentBirthday: String
var studentClass: Int
var studentDept: String
var studentFaculty: String
var studentIsActive: Bool
var studentIsGraduated: Bool
var studentLectures: [String]?
var studentGrades: [Grades]?
}
StudentsViewModel.swift
import Foundation
import Firebase
class StudentsViewModel: ObservableObject {
@Published var students = [Students]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("Students").whereField("id", isEqualTo: "20704029").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.students = documents.map { (queryDocumentSnapshot) -> Students in
let data = queryDocumentSnapshot.data()
let id = data["id"] as? String ?? ""
let email = data["email"] as? String ?? ""
let studentName = data["studentName"] as? String ?? ""
let studentSurname = data["studentSurname"] as? String ?? ""
let studentTerm = data["studentTerm"] as? String ?? ""
let studentBirthday = data["studentBirthday"] as? String ?? ""
let studentClass = data["studentClass"] as? Int ?? 0
let studentDept = data["studentDept"] as? String ?? ""
let studentFaculty = data["studentFaculty"] as? String ?? ""
let studentIsActive = data["studentIsActive"] as? Bool ?? true
let studentIsGraduated = data["studentIsGraduated"] as? Bool ?? false
let studentLectures = data["studentLectures"] as? Array ?? [""]
let student = Students(id: id, email: email, studentName: studentName, studentSurname: studentSurname, studentTerm: studentTerm, studentBirthday: studentBirthday, studentClass: studentClass, studentDept: studentDept, studentFaculty: studentFaculty, studentIsActive: studentIsActive, studentIsGraduated: studentIsGraduated, studentLectures: studentLectures)
//print(student)
return student
}
}
}
}
GradesPage.swift
import SwiftUI
struct GradesPage: View {
@ObservedObject private var viewModel = StudentsViewModel()
var body: some View {
NavigationView {
List(viewModel.students) { student in
VStack(alignment: .leading) {
Text("Student Name: ") + Text(" ") + Text(student.studentName) + Text(" ") + Text(student.studentSurname)
//I want to add the grades to see in here but don't know how to handle it
}
}
}.onAppear {
self.viewModel.fetchData()
}
}
}
Solutions that I tried:
Firestore Official Documentation
Thanks in advance, all helps are welcome!
Note, you should be using
@StateObject private var viewModel = StudentsViewModel()
.
Also note, there is no finalTerm
in your code
and NavigationView
is deprecated, use NavigationStack
To display studentGrades
in your GradesPage
View,
simply use a ForEach
, such as:
struct GradesPage: View {
@StateObject private var viewModel = StudentsViewModel()
var body: some View {
NavigationStack { // <--- here
List(viewModel.students) { student in
VStack(alignment: .leading) {
Text("Student Name: ") + Text(" ") + Text(student.studentName) + Text(" ") + Text(student.studentSurname)
ForEach(student.studentGrades ?? []) { grade in // <--- here
Text(grade.lectureGradeId)
Text(grade.midTerm)
Text(grade.endTerm)
}
}
}
}
.onAppear {
self.viewModel.fetchData()
}
}
}
struct Grades: Identifiable, Codable, Hashable { //<--- here
let id = UUID()
var lectureGradeId: String
var midTerm: String
var endTerm: String
enum CodingKeys: String, CodingKey {
case lectureGradeId, midTerm, endTerm
}
}
struct Students: Identifiable, Codable {
var id: String
var email: String
var studentName: String
var studentSurname: String
var studentTerm: String
var studentBirthday: String
var studentClass: Int
var studentDept: String
var studentFaculty: String
var studentIsActive: Bool
var studentIsGraduated: Bool
var studentLectures: [String]?
var studentGrades: [Grades]?
}
class StudentsViewModel: ObservableObject {
@Published var students = [Students]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("Students").whereField("id", isEqualTo: "20704029").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.students = documents.map { (queryDocumentSnapshot) -> Students in
let data = queryDocumentSnapshot.data()
let id = data["id"] as? String ?? ""
let email = data["email"] as? String ?? ""
let studentName = data["studentName"] as? String ?? ""
let studentSurname = data["studentSurname"] as? String ?? ""
let studentTerm = data["studentTerm"] as? String ?? ""
let studentBirthday = data["studentBirthday"] as? String ?? ""
let studentClass = data["studentClass"] as? Int ?? 0
let studentDept = data["studentDept"] as? String ?? ""
let studentFaculty = data["studentFaculty"] as? String ?? ""
let studentIsActive = data["studentIsActive"] as? Bool ?? true
let studentIsGraduated = data["studentIsGraduated"] as? Bool ?? false
let studentLectures = data["studentLectures"] as? Array ?? [""]
// <--- something like this, adjust as needed, don't remember how it works
let studentGrades = data["studentGrades"] as? [Grades] ?? []
let student = Students(id: id, email: email, studentName: studentName, studentSurname: studentSurname, studentTerm: studentTerm, studentBirthday: studentBirthday, studentClass: studentClass, studentDept: studentDept, studentFaculty: studentFaculty, studentIsActive: studentIsActive, studentIsGraduated: studentIsGraduated, studentLectures: studentLectures,
studentGrades: studentGrades //<--- here
)
//print(student)
return student
}
}
}
}
Note, you could try mapping the results of the firebase query directly into
your codable Students
struct, see for example:
https://firebase.google.com/docs/firestore/solutions/swift-codable-data-mapping and https://firebase.google.com/docs/firestore/query-data/get-data
For example, from: https://firebase.google.com/docs/firestore/solutions/swift-codable-data-mapping
func fetchData() {
let docRef = db.collection("Students").whereField("id", isEqualTo: "20704029")
docRef.getDocument(as: Students.self) { result in
switch result {
case .success(let students):
self.students = students
case .failure(let error):
print("Error decoding document: \(error)")
// todo deal with errors
}
}
}