Swift Firestore Get Document ID - ios

I'm using FirebaseFirestore for my database.
I have an array of Lists with a name and description. I'd also like to grab each lists unique documentId. Is this possible?
List.swift
struct List {
var name:String
var description:String
var dictionary:[String:Any] {
return [
"name":name,
"description":description
]
}
}
ListTableViewController.swift
func getLists() {
if Auth.auth().currentUser != nil {
db.collection("lists").getDocuments { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
self.listArray = querySnapshot!.documents.flatMap({List(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
I found you can get the documentId by doing...
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
But how do I store this in my List to be able to call later?

1.Suppose you are getting documents in snapshot.
guard let documents = ducomentSnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
// Get documentId of each document objects using this code.
for i in 0 ..< documents.count {
let dictData = documents[i].data()
let documentID = documents[i].documentID
print("Document ID \(documentID)")
}

I'm not positive if this is the best way but I appended to the listArray and it seems to work.
self.listArray.append(List(name: document["name"] as! String, description: document["description"] as! String, documentId: document.documentID))

Add an id attribute to your list struct:
struct List {
var name:String
var description:String
var id: String?
var dictionary:[String:Any] {
return [
"name":name,
"description":description
]
}
}
Then when mapping your documents manually assign the id attribute:
list = documents.compactMap{ (queryDocumentSnapshot) -> List? in
var listItem = try? queryDocumentSnapshot.data(as: List.self)
listItem?.id = queryDocumentSnapshot.documentID
return listItem
}

Related

getting documents from a subcollection Firestore swiftUI

I'm trying to retrieve data from documents in a subCollection in Firestore, but it returns an empty array after executing it.
I tried to do the following.
#Published var OrdersID = [String]()
func fetchOrdersID(){
FirebaseManager.shared.firestore.collection("Users").document(id).collection("OrdersID").getDocuments { snapshot, error in
guard let documents = snapshot?.documents else {
print("No documents")
return
}
DispatchQueue.main.async {
self.OrdersID = documents.map{ d in
return d["id"] as? String ?? ""
}
}
}
}
but still it returns an empty array of OrdersID outside this function, and inside of it returns the proper array list(IMAGE 1 shows this, orders ID is the content of the OrdersID array IMAGE 1)
You could try escaping with a completion..
#Published var OrdersID = [String]()
func fetchOrdersID(withCompletion completion: #escaping ((_ orderID: String) -> (Void))) {
FirebaseManager.shared.firestore.collection("Users").document(id).collection("OrdersID").getDocuments { snapshot, error in
guard let documents = snapshot?.documents else {
print("No documents")
return
}
let orderID = documents["id"] as? String ?? ""
completion(orderID)
}
}
And then when you go to call, you can add to the OrdersId...
fetchOrdersID { orderID in
OrdersID.append(orderID)
}

SwiftUI: How to set UserDefaults first time view renders?

So I have this code, where I fetch a url from firestore and then append it to an array, which is then stored in userDefaults(temporarily).
In the view I basically just iterate over the array stored in userdefaults and display the images.
But the problem is, that I have to rerender the view before the images show.
How can i fix this?
struct PostedImagesView: View {
#State var imagesUrls : [String] = []
#ObservedObject var postedImagesUrls = ProfileImages()
var body: some View {
VStack{
ScrollView{
ForEach(postedImagesUrls.postedImagesUrl, id: \.self) { url in
ImageWithURL(url)
}
}
}
.onAppear{
GetImage()
print("RAN GETIMAGE()")
}
}
// Get Img Url from Cloud Firestore
func GetImage() {
guard let userID = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
db.collection("Users").document(userID).collection("PostedImages").document("ImageTwoTester").getDocument { (document, error) in
if let document = document, document.exists {
// Extracts the value of the "Url" Field
let imageUrl = document.get("Url") as? String
UserDefaults.standard.set([], forKey: "postedImagesUrls")
imagesUrls.append(imageUrl!)
UserDefaults.standard.set(imagesUrls, forKey: "postedImagesUrls")
} else {
print(error!.localizedDescription)
}
}
}
}

Fetch Firebase data with For loop in SwiftUI

I am trying to fetch specific Firebase data with a for loop but it gives me this error: "'ListenerRegistration' cannot be used as a type conforming to protocol 'View' because 'View' has static requirements". The Code runs if the ZStack{}.onAppear() function is called. How can I change that?
ForEach(self.allFriends, id: \.id) { friend in
Firestore.firestore().collection(friend.email+"-TodayCal").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("error fetching: \(error!)")
return
}
let name = documents.map { $0["nameOfFriend"] }
print(name)
self.allFriendsCal.removeAll()
for i in 0..<name.count {
self.allFriendsCal.append(AllFriendsCal(id: UUID(uuidString: documents[i].documentID) ?? UUID(), name: name[i] as? String ?? "Failed to get Name"))
}
}
}
ForEach is a View type, which takes in other Views in the function builder. You can achieve what you want by calling the Firebase collection in onAppear() or wherever else you already have allFriends populated and iterating through the results like so:
.onAppear {
for friend in self.allFriends {
Firestore.firestore().collection(friend.email+"-TodayCal").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("error fetching: \(error!)")
return
}
let name = documents.map { $0["nameOfFriend"] }
print(name)
self.allFriendsCal.removeAll()
for i in 0..<name.count {
self.allFriendsCal.append(AllFriendsCal(id: UUID(uuidString: documents[i].documentID) ?? UUID(), name: name[i] as? String ?? "Failed to get Name"))
}
}
}
}

Swift Does not see data

I am trying to parse the data and display on the screen but i am getting " Value of type 'EmployeeData' has no member 'employee_name' "
What i am missing ?
I created my struct, parsed data and tried to divide into two parts. first part will be related with listing, second part is all data.
struct EmployeeData: Codable {
var data: Employee
var status: String
}
struct Employee: Codable {
var employee_name: String
var employee_salary: String
var employee_age: String
}
class WebServices {
func getData(completion: #escaping (EmployeeData?) -> ()){
guard let url = URL(string:"http://dummy.restapiexample.com/api/v1/employees")
else { fatalError("There is error!") }
URLSession.shared.dataTask(with: url) { (data, response,error) in
guard let data = data, error == nil else {
DispatchQueue.main.async{
completion(nil)
}
return
}
let empleyees = try? JSONDecoder().decode(EmployeeData.self, from: data)
DispatchQueue.main.async {
completion(empleyees)
}
}.resume()
}
}
class MVDesingnListView: ObservableObject {
}
struct MVDesignCellView {
let employeeDatas: EmployeeData
init(employeeDatas: EmployeeData) {
self.employeeDatas = employeeDatas
}
var employee_name: String {
self.employeeDatas.employee_name
}
}
The compiler is all right. Your struct EmployeeData has no member employee_name.
You need to go to the employee first, to get her name:
var employee_name: String {
self.employeeDatas.data.employee_name
}
should do the job.

Updating Firestore Database causes iOS crash

When I update the firebase firestore database with any new field, it instantly kills any app running that uses the data with the fatal error in the code below.
The error I get says "fatalError: "Unable to initialize type Restaurant with dictionary [(name: "test", availability: "test", category: "test")]
I'd like to be able to update it without having the apps crash. If that happens, they have to delete and reinstall the app to get it to work again, so I think it's storing the data locally somehow, but I can't find where.
What can I do to make this reset the data or reload without crashing?
The file where the error is thrown (when loading the table data):
fileprivate func observeQuery() {
stopObserving()
guard let query = query else { return }
stopObserving()
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Restaurant in
if let model = Restaurant(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
}
}
self.restaurants = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
}
}
And the Restaurant.swift file:
import Foundation
struct Restaurant {
var name: String
var category: String // Could become an enum
var availability: String // from 1-3; could also be an enum
var description: String
var dictionary: [String: Any] {
return [
"name": name,
"category": category,
"availability": availability,
"description": description
]
}
}
extension Restaurant: DocumentSerializable {
//Cities is now availability
static let cities = [
"In Stock",
"Back Order",
"Out of Stock"
]
static let categories = [
"Rock", "Boulder", "Grass", "Trees", "Shrub", "Barrier"
]
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String,
let description = dictionary["description"] as? String
else { return nil }
self.init(name: name,
category: category,
availability: availability,
description: description
)
}
}
The Local Collection File with the Document.Serializable code:
import FirebaseFirestore
// A type that can be initialized from a Firestore document.
protocol DocumentSerializable {
init?(dictionary: [String: Any])
}
final class LocalCollection<T: DocumentSerializable> {
private(set) var items: [T]
private(set) var documents: [DocumentSnapshot] = []
let query: Query
private let updateHandler: ([DocumentChange]) -> ()
private var listener: ListenerRegistration? {
didSet {
oldValue?.remove()
}
}
var count: Int {
return self.items.count
}
subscript(index: Int) -> T {
return self.items[index]
}
init(query: Query, updateHandler: #escaping ([DocumentChange]) -> ()) {
self.items = []
self.query = query
self.updateHandler = updateHandler
}
func index(of document: DocumentSnapshot) -> Int? {
for i in 0 ..< documents.count {
if documents[i].documentID == document.documentID {
return i
}
}
return nil
}
func listen() {
guard listener == nil else { return }
listener = query.addSnapshotListener { [unowned self] querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> T in
if let model = T(dictionary: document.data()) {
return model
} else {
// handle error
fatalError("Unable to initialize type \(T.self) with local dictionary \(document.data())")
}
}
self.items = models
self.documents = snapshot.documents
self.updateHandler(snapshot.documentChanges)
}
}
func stopListening() {
listener = nil
}
deinit {
stopListening()
}
}
fatalError: "Unable to initialize type Restaurant with dictionary [(name: "test", availability: "test", category: "test")]
Seems pretty straightforward - that dictionary does not contain enough information to create a Restaurant object.
The error is from
if let model = Restaurant(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
}
because your initializer returns a nil value, from:
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String,
let description = dictionary["description"] as? String
else { return nil }
self.init(name: name,
category: category,
availability: availability,
description: description
)
}
because your guard is returning nil because you do not have a description key in the dictionary.
To fix, either put a description key in the dictionary OR change your initializer to use a default description when the key is missing.
For example, here is your initializer, rewritten to use a default description, for when the description entry is missing
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String
else { return nil }
let description = dictionary["description"] as? String
let defaultDescription: String = description ?? "No Description"
self.init(name: name,
category: category,
availability: availability,
description: defaultDescription
)
}

Resources