Cannot reach to members of an array with index - firebase

I am trying to fill an array with data from Firebase. But after filling the array I can't call its' members from their index. I am filling the array by this function
func loadSounds(){
Firestore.firestore().collection("data").getDocuments{ (snapshot, error) in
if error == nil{
for document in snapshot!.documents{
let name = document.data()["name"] as? String ?? "error"
let sounds = document.data()["sounds"] as? [String : [String : Any]]
var soundsArray = [dataSound]()
if let sounds = sounds{
for sound in sounds {
let soundName = sound.value["name"] as? String ?? "error"
let soundImage = sound.value["image"] as? String ?? "error"
soundsArray.append(dataSound(name: soundName , image: soundImage ))
}
}
categoriesArray.append(Category(category: name , sounds: soundsArray))
}
print(categoriesArray[0].category)
} else {
print(error)
}
} }
When I try to access it from View, It gives index out of bounds error.
struct ContentView: View {
init(){
loadSounds()
}
var body: some View {
Text(categoriesArray[0].category)}}
If I try to access it via ForEach, it works, also when I try to print it from loadSounds function it works, but I need to access them from their index in View. Thanks for any help.

Never access items of an array by index in the rendering area of a SwiftUI View, in almost all cases the array is empty when the view is rendered the first time.
In your case use .first and handle the optional
var body: some View {
Text(categoriesArray.first?.category ?? "No value")}} // or empty string

Related

How do I map my ViewModel's ID to the Document ID in Firestore?

I have the fetch Data code here, but I don't understand how I am supposed to delete documents without setting the ID to the Document's ID. I was following this tutorial here. https://medium.com/swift-productions/swiftui-easy-to-do-list-with-firebase-2637c878cf1a I'm assuming I need to do so in the data mapping but I don't understand how with this code. I want to remove a todo from a SwiftUI list and also delete it's entire Firestore Document.
func fetchData() {
db.collection("todos").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.todos = documents.map { (QueryDocumentSnapshot) -> Todo in
let data = QueryDocumentSnapshot.data()
let todoDetails = data["todo"] as? String ?? ""
return Todo(todoDetais: todoDetails)
}
}
}
View Model
struct Todo: Codable, Identifiable {
var id: String = UUID().uuidString
var todoDetais: String?
}
I recommend using Codable to map your Firestore documents to Swift structs. This will make your code easier to write, less prone to errors, and more type-safe.
Specifically, it will also enable you to use #DocumentID to map the Firestore document ID to the id attribute of your Swift struct.
Here's a quick example:
struct Book: Codable {
#DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.book = try document.data(as: Book.self)
}
catch {
print(error)
}
}
}
}
}
For more details, see this comprehensive guide I wrote about mapping Firestore documents to Swift structs (and back).
For more information about how to delete a Firestore document from a SwiftUI app, check out this article

Swift code to Add item with quantity in Firebase Database

Using Swift code 5.1 I have managed to update Firestore Database with items in current users basket but not able to add/update quantity. Currently if I wanted to add an item that already exist in the basket it simply adds another line but I wanted to just update quantity.
Can you advise me on how to create a function that adds quantity?
Here are the codes I have so far. Only relevant sections of code pasted.
Firestore DB function in my Helper file:
enum FCollectionReference: String {
case User
case Category
case Items
case Basket
case Orders
}
func FirebaseReference(_ collectionReference: FCollectionReference) -> CollectionReference {
return Firestore.firestore().collection(collectionReference.rawValue)
}
Here's the code in in my Basket Model file using
class Basket {
var id: String!
var ownerId: String!
var itemIds: [String]!
var delivery: Float!
var admin: Float!
var quantity: Int!
init() {
}
init(_dictionary: NSDictionary) {
id = _dictionary[kOBJECTID] as? String
ownerId = _dictionary[kOWNERID] as? String
itemIds = _dictionary[kITEMIDS] as? [String]
delivery = _dictionary[kDELIVERY] as? Float
admin = _dictionary[kADMIN] as? Float
quantity = _dictionary[kQUANTITY] as? Int
}
}
//MARK: Helper functions
func basketDictionaryFrom(_ basket: Basket) -> NSDictionary {
return NSDictionary(objects: [basket.id, basket.ownerId, basket.itemIds, basket.quantity], forKeys: [kOBJECTID as NSCopying, kOWNERID as NSCopying, kITEMIDS as NSCopying, kQUANTITY as NSCopying,kDELIVERY as NSCopying, kADMIN as NSCopying])
}
//MARK: - Update basket
func updateBasketInFirestore(_ basket: Basket, withValues: [String : Any], completion: #escaping (_ error: Error?) -> Void) {
FirebaseReference(.Basket).document(basket.id).updateData(withValues) { (error) in
completion(error)
Codes in Item View Control to add items to basket:
#objc func addToBasketButtonPressed() {
//check if user is logged in or show login view
if MUser.currentUser() != nil {
downloadBasketFromFirestore(MUser.currentId()) { (basket) in
if basket == nil {
self.createNewBasket()
}else {
basket?.itemIds.append(self.item.id)
self.updateBasket(basket: basket!, withValues: [kITEMIDS: basket!.itemIds])
}
}
} else {
showLoginView()
}
}
private func updateBasket(basket: Basket, withValues: [String : Any]) {
updateBasketInFirestore(basket, withValues: withValues) { (error) in
if error != nil {
self.hud.textLabel.text = "Error: \(error!.localizedDescription)"
self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
print("error updating basket", error!.localizedDescription)
}else {
self.hud.textLabel.text = "Added to Basket"
self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
}
}
}
To clarify my request, what do I need to change/re-arrange in my coding so the Database Cloud Firestore is arranged in order shown in my attached screen shot. First screen shot showing current layout in the last column and I'm trying to change this to layout demonstrated in the second screen shot?
I think you are asking how to update the value in a field within a Firestore document. If not, let me know and I will update the answer.
Here's some code that updates the qty of an item in inventory. Pass in the qty to add as a + Int and then to subtract as a - Int. The structure looks like this
root
inventory
item_0
qty: 0
and the code to update the qty node is:
func incrementQty(deltaQty: Int) {
let docToUpdate = self.db.collection("inventory").document("item_0")
docToUpdate.updateData( [
"qty": FieldValue.increment( Int64(deltaQty) )
])
}
call it like this
self.incrementQty(deltaQty: 4) //adds 4 to the existing qty
previously, incrementing values had to be wrapped into a transaction to make it safe but the FieldValue makes it much easier.
I am adding another answer based on comments and question clarification. My other answer still stands as an answer but it's a different approach.
Arrays are inherently hard to work with in NoSQL databases as they are often treated as a single object. They have limited functionality opposed to collections, documents and fields, and can't directly be sorted or have items inserted. And querying is well, challenging. Firestore does a great job at providing better interoperability with arrays but there are still usually better options.
Instead of an array, I would change the structure to this:
Baskets (collection)
basket_number (document in the Baskets collection, like you have now)
items //a collection of items in the basket
item_0 //a document with the docID being the the item number
item_qty: //qty of the item
item_1
item_qty:
item_2
item_qty:
So the downside of .updateData is that if the field being updated doesn't exist, it doesn't create the field, it simply throws an error. So we need to test to see if the document exists first, if so, update with updateData, if not create the item with an initial quantity.
Here's the code that does it - note for simplicity I am ignoring the top level Basket and basket_number since you already know how to do that part and focused on the items collection and down.
func incrementQty(itemNumberToUpdate: String, deltaQty: Int) {
let docToUpdate = self.db.collection("items").document(itemNumberToUpdate)
docToUpdate.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let _ = documentSnapshot?.data() {
print("item exists, update qty")
docToUpdate.updateData([
"item_qty": FieldValue.increment( Int64(deltaQty) )
], completion: { err in
if let err = err {
print("Error updating document: \(err.localizedDescription)")
} else {
print("Item qty successfully updated")
}
})
} else {
print("no item exists, need to create")
docToUpdate.setData([
"item_qty": FieldValue.increment( Int64(deltaQty) )
], completion: { err in
if let err = err {
print("Error updating document: \(err.localizedDescription)")
} else {
print("Item successfully created with initial quantity")
}
})
}
})
}
Pass in an item number and the quantity to either modify the existing qty by, or will be the initial quantity.
self.incrementQty(itemNumberToUpdate: "item_0", deltaQty: 5)

Could not cast value of type '__NSDictionaryM' (0x1111152b0) to 'FIRDataSnapshot' Firebase Swift 3

I am trying to read nested data structures from Firebase Database, but I don't know how to manage the case when an object of type [String:AnyObject] could be nil.
When readFeesCleaner(callback_) is called, it throws an error.
func readFeesCleaner(callback: #escaping ((_ feesCleaner: FeesCleaner) -> Void)) {
dbRef.child("FeesCleaner").child(self.uidOfTextField!).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
guard !(snapshot.value is NSNull) else {
return
}
//throws error: signal SIGABRTCould not cast value of type '__NSDictionaryM' (0x1111152b0) to 'FIRDataSnapshot' (0x10ef16d18).
let feesCleanersReceived = FeesCleaner(snapshot: (snapshot.value)! as! FIRDataSnapshot)
callback(feesCleanersReceived)
}) { (error:Error) in
print(#line, "\(error.localizedDescription)")
}
}
struct FeesCleaner {
var outstandingFees: AnyObject!
var timeStampFeesSaved: [String:AnyObject]!
var backgroundCheck: AnyObject!
init(
outstandingFees: AnyObject? = nil, //value might not exist when reading
timeStampFeesSaved: [String:AnyObject]? = nil,// value might not exist when reading
backgroundCheck: AnyObject) {
self.outstandingFees = outstandingFees
self.timeStampFeesSaved = timeStampFeesSaved
self.backgroundCheck = backgroundCheck
}//end of init
//read data here
[full struct data here][1]
https://gist.github.com/bibscy/dc48f7107459379e045a50fdbbc35335
}//end of struct
There's a number of issues here. First:
how to manage the case when an object of type [String:AnyObject]
could be nil.
You've handled that with the prior statement, noting that you can also add
if snapshot.exists == false {return}
Second: You've got to handle optionals properly - if a var could be nil, you need code in place to handle that situation and not plow through it. If you force unwrap an optional you are essentially stating that for sure, it will never be nil, so basically, don't do that.
One fix could be to simply pass the snapshot as a DataSnapshot and then pull out the properties one at a time; if they exist, assign them, if not set to 0 or nil or some other placeholder.
Something like this inside the Firebase closure:
let feesCleanersReceived = FeesCleaner(withSnapshot: snapshot)
and then your struct like this: note we are leveraging the nil coalescing operator, ??
struct FeesCleanerStruct {
var outstandingFees: String?
var timeStampFeesSaved: String?
init(withSnapshot: DataSnapshot) {
let dict = withSnapshot.value as! [String: Any]
self.outstandingFees = dict["outstandingFees"] as? String ?? "0.0"
self.timeStampFeesSaved = dict["timeStampFeesSaved"] as? String ?? "0"
}
}

Iteration through dictionaries, Firebase snapshot using

I am having a problem while iterating through a dictionary. First check my database
And here is my code
ref.child("Events").observe(.value, with: { (snapshot) in
for child in snapshot.children{
let snap = child as! DataSnapshot
let valueSnap = snap.value as? [[String:Any]]
let nombreEventDansClub = valueSnap?.count
print(valueSnap)
for index in 0...nombreEventDansClub!-1
{
print(valueSnap![index]["name"])
//print(valueSnap![index]["end_time"])
if(valueSnap![index]["end_time"] as? String == nil)
{
//do Something
}
else{
let end_time_test = valueSnap![index]["end_time"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+2:00")
let end_test = dateFormatter.date(from: end_time_test!)!
The recuperation is perfect but for example : if Event-0-(3) didn't exist the app would crash, I know the problem is that I calculate the number of events in a : Event-(something) and iterate according to this. I was wondering if there is a smarter way. Thanks in advance
You can parse the snapshot this way:-
ref.child("Events").observe(.value, with: { (snapshot) in
guard let values = snapshot as? [Any] else { return }
for value in values {
guard let childrens = value as? [Any] else { continue }
for children in childrens {
guard let dict = children as? [String: Any] else { continue }
for key in dict.allkeys {
// TO debug all keys and values
print(dict[key])
}
print(dict["description"] as? String)
print(dict["end_time"] as? String)
print(dict["id"] as? String)
// other keys
}
}
}
Force unwrapping may cause your app crash unexpectedly.

Cannot determine type of json object returned from web service

I have an old type of web service built on ASP.Net. With the following function, I was able to fetch some type of data from the asmx web service:
func getJsonData(sql: String, spparamnames: String, spParamValues: String, completeonClosure: #escaping (AnyObject?) -> ()) {
let url = URL(string:"http://www.example.com/MyWebService.asmx/GetDataTableAsJson")
var request = URLRequest(url: url!)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") // the request is JSON
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept") // the expected response is also JSON
request.httpMethod = "POST"
let dictionary = ["sql" : sql, "spparamnames" : spparamnames, "spparamvalues" : spParamValues] //Parameters are here seperated with comma
request.httpBody = try! JSONSerialization.data(withJSONObject: dictionary)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error.debugDescription) // some fundamental network error
return
}
do {
if response != nil {
let myJson = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String:AnyObject]
let isCorectJson = JSONSerialization.isValidJSONObject(myJson)
let charcount = (myJson["d"] as? String)?.characters.count
let cc = charcount ?? 0
if isCorectJson == true && cc > 0 {
completeonClosure(myJson as AnyObject?)
)
} else {
let str2 = "Connection Error"
completeonClosure(str2 as AnyObject?)
}
}
} catch let JsonError {
print(JsonError)
}
}
task.resume()
}
When I run a query with Swift, and cast the object type as NSDictionary, my output result is the following:
getJsonData(sql: "SELECT TOP 3 User_id, LoweredUserName FROM Users", spparamnames: "", spParamValues: "") {
returnJSON in
OperationQueue.main.addOperation {
let mystr = returnJSON as? NSDictionary
print(mystr!)
}
}
Result:
{
d = "[{\"User_id\":102,\"LoweredUserName\":\"abu alay\"},{\"User_id\":90,\"LoweredUserName\":\"ali es\"},{\"User_id\":95,\"LoweredUserName\":\"alper ay\"}]";
}
I think that the result is some kind of dictionary, I was not able to convert the result to an array, therefore I cannot iterate between the rows and use the result efficiently. What should I do in order to read the result like: print(returnJSON[0]["LoweredUserName"]) ? What is the meaning of the letter "d" at the beginning of the result? Many thanks in advance.
It looks like your response is an Array, try to cast to an array of dictionary objects.
if let myJson = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [[String:AnyObject]] {
// parse each object here
}
This example code I ran in a playground seems to work fine:
let jsonString = "[{\"User_id\":102,\"LoweredUserName\":\"abu alay\"},{\"User_id\":90,\"LoweredUserName\":\"ali es\"},{\"User_id\":95,\"LoweredUserName\":\"alper ay\"}]"
let jsonData = jsonString.data(using: String.Encoding.utf8)
if let json = try? JSONSerialization.jsonObject(with: jsonData!, options: .mutableContainers) as? [[String:AnyObject]] {
print(json)
}
Output:
Optional([["User_id": 102, "LoweredUserName": abu alay], ["User_id": 90, "LoweredUserName": ali es], ["User_id": 95, "LoweredUserName": alper ay]])
If the text you have shown is the entire body of the result you got:
{
d = "[{\"User_id\":102,\"LoweredUserName\":\"abu alay\"},{\"User_id\":90,\"LoweredUserName\":\"ali es\"},{\"User_id\":95,\"LoweredUserName\":\"alper ay\"}]";
}
Then this is not properly formatted JSON. For it to be formatted properly the "d" would have to be shown in double quotes.
It looks like you may need to do some custom parsing on the result to get at the JSON contained in the "d" area.

Resources