Must be a non-empty string and not contain '.' '#' '$' '[' or ']'' - swift

Please help! I am experiencing an app crash.
public enum memberships {
case noMembership
case paid
case freeTrial
case trialExpired
}
public class DataManager {
private static let uuid = UIDevice.current.identifierForVendor!.uuidString
private static let user = Auth.auth().currentUser
private static let userRef = Database.database().reference().child("Users").child(user?.uid ?? "")
static var currentStatus: memberships? = nil
/**
Get's the current user's info from Firebase
and returns the info as a User object
*/
class func getUser(completion: #escaping (User?) -> ()) {
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
// UIPasteboard.general.string = uuid
let uuid = snapshot.key
let name = value["name"] as? String ?? ""
let email = value["email"] as? String ?? ""
let dateJoined = value["dateJoined"] as? String ?? ""
let membershipString = value["membership"] as? String
let alertsStartTime = value["alertsStartTime"] as? String ?? ""
let alertsEndTime = value["alertsEndTime"] as? String ?? ""
let alertsFrequency = value["alertsFrequency"] as? Int ?? 1
let alertsType = value["alertsType"] as? String ?? ""
let isEnable = value["isEnable"] as? Bool ?? true
//Gets users current membership
var membershipStatus: memberships!
if membershipString == "Paid" {
membershipStatus = .paid
}else if membershipString == "NoMembership" {
membershipStatus = .noMembership
}else{
membershipStatus = Utils.getUserMembershipStatus(dateJoined: dateJoined)
}
let user = User(uuid: uuid, name: name, email: email, dateJoined: dateJoined, membership: membershipStatus, alertsStartTime: alertsStartTime, alertsEndTime: alertsEndTime, alertsType: alertsType, alertsFrequency: alertsFrequency, isEnable: isEnable)
completion(user)
}else{
completion(nil)
}
}) { (error) in
print(error.localizedDescription)
completion(nil)
}
}

Your user object is empty. So user?.uid is nil. Which means child(user?.uid ?? "") -> child("").
Firebase does not accept empty strings as key values (It also does not accepts strings which includes '.' '#' '$' '[' or ']'' as keys).
So in your case make sure user is logged or use different key value.

Related

Swift Initializer Variable 'self._' used before being initialized

I have a struct called MyAccount shown below and it's giving me this error
Variable self.record used before being initialized
for the line self.record = record. I'm confused what is going wrong here as I'm passing a CKRecord to self.record and I thought that should work. Any ideas? Thanks!
struct MyAccount: Hashable ,Identifiable, CloudItem {
var record: CKRecord
var id = UUID()
var network: NetworksDataModel.Networks
var username: String
init?(record: CKRecord) {
guard let newNetworkString = record.object(forKey: "network") as? String else { return }
guard let newNetwork = NetworksDataModel.Networks(rawValue: newNetworkString) else { return }
guard let newUsername = record.object(forKey: "username") as? String else { return }
self.network = newNetwork
self.username = newUsername
self.record = record
}
init?(network: NetworksDataModel.Networks, username: String) {
var record = CKRecord(recordType: "account")
record["network"] = network.rawValue
record["username"] = username
self.init(record: record)
}
}
I was thinking maybe I need to make the CKRecord optional but Im not sure why I would need to do that. I was thinking the above code should work.
To denote that a failable initializer is failing you have to return nil.
The three guard statements can be combined to one
init?(record: CKRecord) {
guard let newNetworkString = record.object(forKey: "network") as? String,
let newNetwork = NetworksDataModel.Networks(rawValue: newNetworkString),
let newUsername = record.object(forKey: "username") as? String else { return nil }
self.network = newNetwork
self.username = newUsername
self.record = record
}

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

Using Firebase in Swift, how can I pull documents from a collection to be displayed in a text view?

After logging into the app, in a settings page, SettingsViewController, I would like for the user to be able to see their information in a text view. However, no matter the approach to reading the data, i always get the Swift Compiler Warning:
Cast from '[String]' to unrelated type 'String' always fails
This is my code:
func textViewFill() {
let db = Firestore.firestore()
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let firstName = ["firstName"] as? String ?? ""
let lastName = ["lastName"] as? String ?? ""
let email = ["email"] as? String ?? ""
self.firstNameTextView.text = firstName
self.lastNameTextView.text = lastName
self.emailTextView.text = email
}
}
}
}
The warning appears for when I am trying to define the documents as strings:
let firstName = ["firstName"] as? String ?? ""
let lastName = ["lastName"] as? String ?? ""
let email = ["email"] as? String ?? ""
Is there no way to display it as text?
You're trying to cast an Array as String here. Here's what you need:
let firstName = document.get("firstName") as? String ?? ""
let lastName = document.get("lastName") as? String ?? ""
let email = document.get("email") as? String ?? ""
You can use if let to unwrap optionals and dont cast Array as string .. it will always fails..
func textViewFill() {
let db = Firestore.firestore()
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let document = querySnapshot!.documents.first {
print("\(document.documentID) => \(document.data())")
if let firstName = document.get("firstName") as? String ,
let lastName = document.get("lastName") as? String,
let email = document.get("email") as? String {
self.firstNameTextView.text = firstName
self.lastNameTextView.text = lastName
self.emailTextView.text = email
}
}
}
}
}

Setting up Nested Structs and populating via FireStore [Swift]

I have two structs, one nested within the other:
struct User {
let uid: String
let name: String
var pack: [Doggo]
}
struct Doggo {
let dogUid: String
let dogName: String
let dogBreed: String
let dogBorn: String
let dogProfileImageURL: String
}
Is the following code the proper way to access the firebase data for each?
guard let userUid = Auth.auth().currentUser?.uid else { return }
let userRef = self.db.collection("users").document(userUid)
let packRef = self.db.collection("users").document(userUid).collection("pack")
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = document.data()
let name = data?["name"] as? String ?? "Anonymous"
packRef.getDocuments(completion: { (snapshot, error) in
if let err = error {
debugPrint("Error fetchings docs: \(err)")
} else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let dogUid = data["dogUid"] as? String ?? "No dogUid"
let dogName = data["dogName"] as? String ?? "No dogName"
let dogBreed = data["dogBreed"] as? String ?? "No dogBreed"
let dogBorn = data["dogBorn"] as? String ?? "No dogBorn"
let dogProfileImageURL = data["dogProfileImageURL"] as? String ?? "No dogProfileImageURL"
let newDoggo = Doggo(dogUid: dogUid, dogName: dogName, dogBreed: dogBreed, dogBorn: dogBorn, dogProfileImageURL: dogProfileImageURL)
self.doggos.append(newDoggo)
print(newDoggo)
}
}
self.user = User(uid: userUid, name: name, pack: self.doggos)
print(self.user)
self.configureHomeController()
self.configureMenuController()
})
} else {
print("Document does not exist")
}
}
It appears to work the way I'd like it to, but I don't want to run into issues down the line as it's quite foundational to the rest of the app.

Instance method takes long time for type-cheking

I have quite a big project that takes a long time to compile. When I checked compilation times of methods. I found that this method, for example, takes usually more than 200ms to compile. What can I do to improve this?
func processSnapshot(snapshot:DataSnapshot) {
self.numberOfUsers.text = "#Total DB (users): \(snapshot.childrenCount.description)"
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let userProfile = UserProfile(snapshot: snapshot) {
if userProfile.picUrl == "" {
numberOfUsersWithOutPicUrl += 1
}
let todayLong = "\(Date())".prefix(10)
if userProfile.createdOnLong.prefix(10) == todayLong {
profileWizardsStartedTotday += 1
if userProfile.weight != "" {
profileWizardsCompletedTotday += 1
}
}
profiles.append(userProfile)
}
}
let numberOfUsersWithOutPicUrlPercent = round(Double(numberOfUsersWithOutPicUrl)*100/Double(numberOfRealUsers))
let sizeOfSnapshotValueInKB:Int = String(describing: snapshot.value).count / 1024
self.userThatAbortedOnBoarding.text = numberOfUsersWithOutPicUrl.description
self.userThatAbortedOnBoardingPercent.text = "\(numberOfUsersWithOutPicUrlPercent)%"
self.userThatStartedOnBoardingToday.text = profileWizardsStartedTotday.description
self.userThatCompletedOnBoardingToday.text = profileWizardsCompletedTotday.description
self.sizeOfSnapshot.text = "\(sizeOfSnapshotValueInKB.description)KB"
}
My UserProfile initializer looks like this:
init?(snapshot: DataSnapshot) {
print("\(snapshot.key) - \(String(describing: snapshot.value))")
guard
let value = snapshot.value as? [String: AnyObject],
let uid = value["uid"] as? String
else {
print("Warning: Userprofile has no UID")
return nil
}
self.uid = uid
self.email = value["email"] as? String ?? ""
self.picUrl = value["picurl"] as? String ?? ""
self.picUrl2 = value["picurl2"] as? String ?? ""
self.picUrl3 = value["picurl3"] as? String ?? ""
self.picUrl4 = value["picurl4"] as? String ?? ""
self.picUrl5 = value["picurl5"] as? String ?? ""
self.picUrl6 = value["picurl6"] as? String ?? ""
self.city = value["city"] as? String ?? ""
self.displayName = value["displayName"] as? String ?? "anonymous"
}