How to query data from FireStore? - swift

I try to query data from Firestore by using a map. My issue is I can't get only one field from the firestore.
The below code is structure of my data model.
UserData.swift
import Foundation
import FirebaseFirestore
struct UsersData {
var BOOKS_Field:Int
var NAME_FIELD:String
var PHOTO_FIELD:String
var USERLEVEL_FIELD:Int
var dictionary:[String:Any] {
return [
"BOOKS_Field":BOOKS_Field,
"NAME_FIELD":NAME_FIELD,
"PHOTO_FIELD":PHOTO_FIELD,
"USERLEVEL_FIELD":USERLEVEL_FIELD
]
}
}
extension UsersData : DocumentSerializable {
init?(dictionary: [String: Any]) {
guard let BOOKS_Field = dictionary["BOOKS_Field"] as? Int,
let NAME_FIELD = dictionary["NAME_FIELD"] as? String,
let PHOTO_FIELD = dictionary["PHOTO_FIELD"] as? String,
let USERLEVEL_FIELD = dictionary["USERLEVEL_FIELD"] as? Int
else {return nil}
self.init(BOOKS_Field: BOOKS_Field, NAME_FIELD: NAME_FIELD, PHOTO_FIELD:PHOTO_FIELD, USERLEVEL_FIELD:USERLEVEL_FIELD)
}
}
This is the place where I try to get users from the Firestorm server.
func getLeadersfromServer() {
let leaderRef = db.collection("USERS_Collection").order(by: "BOOKS_Field", descending: true).limit(to: 5)
leaderRef.addSnapshotListener { (snapShot, error) in
if let error = error {
print(error)
} else {
for document in (snapShot?.documents)! {
let data = document.data()
let docs = (data.compactMap({_ in UsersData(dictionary: data)}))
let user1 = docs[0]
print(user1.NAME_FIELD)
}
}
}
}
You can find the print output result below:
Name1 H.
Name2 B.
How can I get only one name by order?

Related

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

Firestore Swift update text realtime

I have this way of collecting information.
struct MainText {
var mtext: String
var memoji: String
}
class MainTextModel: ObservableObject {
#Published var maintext : MainText!
init() {
updateData()
}
func updateData() {
let db = Firestore.firestore()
db.collection("maintext").document("Main").getDocument { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let memoji = snap?.get("memoji") as! String
let mtext = snap?.get("mtext") as! String
DispatchQueue.main.async {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}
}
}
And such a way of displaying.
#ObservedObject private var viewModel = MainTextModel()
self.viewModel.maintext.memoji
self.viewModel.maintext.mtext
How can I update online without rebooting the view?
Instead of using getDocument, which only gets the document once and doesn't return updates, you'll want to add a snapshot listener.
Here's the Firestore documentation for that: https://firebase.google.com/docs/firestore/query-data/listen
In your case, you'll want to do something like:
db.collection("maintext").document("Main")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let memoji = data["memoji"] as? String, let mtext = data["mtext"] as? String {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

How do I add firestore document Id to my model and subsequently to the array?

I have a struct "Order" that contains a field called orderId:
protocol OrderSerializable {
init?(dictionary:[String:Any])
}
struct Order {
var orderId: String
var status: Int
var currentTotal: Double
var Dictionary:[String : Any] {
return [
"orderId": orderId,
"status": status,
"currentTotal": currentTotal
]
}
}
extension Order : OrderSerializable {
init?(dictionary: [String : Any]) {
guard let orderId = dictionary["orderId"] as? String,
let status = dictionary["status"] as? Int,
let currentTotal = dictionary["currentTotal"] as? Double
else { return nil }
self.init(orderId: orderId, status: status, currentTotal: currentTotal)
}
}
I need to add the firestore document Id to the orderId field in the model array i.e. "ordersArray". How would I go about doing that?
This is my query code so far and I have indicated the line that I need:
orderRef.getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
guard let documents = querySnapshot?.documents else { return }
for document in documents {
let orderDictionary = document.data() as [String : Any]
let order = Order(dictionary: orderDictionary)
// Here I want to append the firestore documentId to order.orderId before appending it to the array
self.ordersArray.append(order!)
}
DispatchQueue.main.async {
self.ordersTableView?.reloadData()
}
}
}
Thanks in advance.
Different error
Modify your extension to accept the documentId as an additional parameter, and pass this to the created Order object.
protocol OrderSerializable {
init?(dictionary:[String:Any], id: String)
}
extension Order : OrderSerializable {
init?(dictionary: [String : Any], id: String) {
guard let status = dictionary["status"] as? Int,
let currentTotal = dictionary["currentTotal"] as? Double
else { return nil }
self.init(orderId: id, status: status, currentTotal: currentTotal)
}
}
Then, when you create each order, pass the documentId as the id parameter.
orderRef.getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
guard let documents = querySnapshot?.documents else { return }
for document in documents {
let orderDictionary = document.data() as [String : Any]
let order = Order(dictionary: orderDictionary, id: document.documentId)
self.ordersArray.append(order!)
}
DispatchQueue.main.async {
self.ordersTableView?.reloadData()
}
}
}
Alternatively, you could have stored the orderId directly in the document itself, so that it would be passed in along with the dictionary, avoiding the need for using documentId.

How to map the nested data in a document from Firestore by Swift?

I have a document data structure on Firestore like this:
pic1
pic2
So there are 2 map-objects inside the document and a collection and a another document inside this document
Then I create 3 model swift files for this document
task:
struct task {
var Name: String
var Address: String
var Car: CarModel
car Price: PriceModel
var dictionary: [String:Any] {
return [
"Name" : Name,
"Address" : Address,
"Car" : CarModel,
"Price" : PriceModel
]
}
init?(data: [String:Any]) {
guard let Name = dictionary["Name"] as? String,
let Address = dictionary["Address"] as? String,
let Car = ditionary["car"] as? CarModel,
let Price = dictionary["price"] as? PriceModel else{
return nil
}
self.Name = Name
self.Address = Address
self.Car = Car
self.Price = Price
}
}
CarModel:
struct CarModel {
var brand: String
var model: String
var year: String
var dictionary: [String:Any] {
return [
"brand" : brand,
"model" : model,
"year" : year,
]
}
init?(data: [String:Any]) {
guard let brand = dictionary["brand"] as? String,
let model = dictionary["model"] as? String,
let year = ditionary["year"] as? String else{
return nil
}
self.brand = brand
self.model = model
self.year = year
}
}
PriceModel:
struct PriceModel {
var basic: Int
var extra: Int
var dictionary: [String:Any] {
return [
"basic" : basic,
"extra" : extra,
]
}
init?(data: [String:Any]) {
guard let basic = dictionary["basic"] as? Int,
let extra = ditionary["extra"] as? Int else{
return nil
}
self.basic = basic
self.extra = extra
}
}
Then download the data with this following code:
func loadDataFromFirestore(completion: #escaping (Bool) -> ()) {
var success: Bool = false
DispatchQueue.global(qos: .userInteractive).async {
let downloadGroup = DispatchGroup()
let colRef = db.collection("tasks")
downloadGroup.enter()
colRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error: \(error)")
return
}
for document in querySnapshot!.documents{
let result = document.data()
print (result)
if let data = task(data: result){
print(data)
}
}
success = true
downloadGroup.leave()
}
downloadGroup.wait()
DispatchQueue.main.async {
completion(success)
}
}
}
I can get the data with comment the CarModel and PriceModel, but if I uncomment these two, it will let my app crash!
So how could I get the map-object to adapt to my code?
And the second question is: How can I get the document inside a document's collection with this kind of code?