after update - Unexpectedly found nil while unwrapping an Optional value - swift

After I've updated my project I get this error:
Unexpectedly found nil while unwrapping an Optional value
class Search {
private var _username: String!
private var _userImg: String!
private var _userKey: String!
private var _userRef: DatabaseReference!
var currentUser = KeychainWrapper.standard.string(forKey: "uid")
var username: String {
return _username <- error
}
var userImg: String {
return _userImg
}
var userKey: String{
return _userKey
}
init(username: String, userImg: String) {
_username = username
_userImg = userImg
}
init(userKey: String, postData: Dictionary<String, AnyObject>) {
_userKey = userKey
if let username = postData["username"] as? String {
_username = username
}
if let userImg = postData["userImg"] as? String {
_userImg = userImg
}
_userRef = Database.database().reference().child("messages").child(_userKey)
}
}
It worked fine under Swift 3 and Firebase 3.0.2, but now, where everything is update, it crashes all the time. It's not a duplicate to any other question as it worked all before.

I am not sure I fully understand the question or what exactly is causing the crash (it's probably a missing child node) or what the use case is of the implicitly unwrapped class vars but in response to a comment, here's what I would do in Swift 4, Firebase 4
Leave your Search class as is except change the init to the following (this is shortened to provide context)
init(withSnap: DataSnapshot) {
_userKey = withSnap.key
let dict = withSnap.value as! [String: Any]
_username = dict["username"] as? String ?? "NO USER NAME!"
_userImg = dict["userImg"] as? String ?? "NO IMAGE"
}
and then the Firebase function to get a user (for example) would look like this
let userRef = self.ref.child("users").child("uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let mySearch = Search(withSnap: snapshot)
print(mySearch.userKey, mySearch.username, mySearch.userImg)
})
You would need to add in the rest of the class code to assign _userRef etc.
The idea here is to provide default values to the required class properties in case one of the Firebase nodes didn't exist and results in nil. i.e. if uid_0 didn't have a Username child node your class would crash (which it is). With the code above, that property would be set to a default value.
And for thoroughness suppose a user node looks like this
users
uid_0: "some string" //the value here is a String, not a Dictionary
that would crash my code. To prevent that, add more error checking in the init
init(withSnap: DataSnapshot) {
_userKey = withSnap.key
if let dict = withSnap.value as? [String: Any] {
_username = dict["username"] as? String ?? "NO USER NAME!"
_userImg = dict["userImg"] as? String ?? "NO IMAGE"
} else {
_username = "No user data"
_userImg = "No user data"
}
}

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
}

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

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.

How do I read a User's Firestore Map to a Swift Dictionary?

I have my user struct with has a dictionary of all their social medias.
struct User: Identifiable {
var id: String { uid }
let uid, email, name, bio, profileImageUrl: String
let numSocials, followers, following: Int
var socials: [String: String]
init(data: [String: Any]) {
self.uid = data["uid"] as? String ?? ""
self.email = data["email"] as? String ?? ""
self.name = data["name"] as? String ?? ""
self.bio = data["bio"] as? String ?? ""
self.profileImageUrl = data["profileImageURL"] as? String ?? ""
self.numSocials = data["numsocials"] as? Int ?? 0
self.followers = data["followers"] as? Int ?? 0
self.following = data["following"] as? Int ?? 0
self.socials = data["socials"] as? [String: String] ?? [:]
}
}
The idea is for socials (the dictionary), to be dynamic, since users can add and remove social medias. Firestore looks like this:
The dictionary is initialized as empty. I have been able to add elements to the dictionary with this function:
private func addToStorage(selectedMedia: String, username: String) -> Bool {
if username == "" {
return false
}
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
print("couldnt get uid")
return false
}
FirebaseManager.shared.firestore.collection("users").document(uid).setData([ "socials": [selectedMedia:username] ], merge: true)
print("yoo")
return true
}
However I can't seem to read the firestore map into my swiftui dictionary. I want to do this so that I can do a ForEach loop and list all of them. If the map is empty then the list would be empty too, but I can't figure it out.
Just in case, here is my viewmodel.
class MainViewModel: ObservableObject {
#Published var errorMessage = ""
#Published var user: User?
init() {
DispatchQueue.main.async {
self.isUserCurrentlyLoggedOut = FirebaseManager.shared.auth.currentUser?.uid == nil
}
fetchCurrentUser()
}
func fetchCurrentUser() {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
self.errorMessage = "Could not find firebase uid"
print("FAILED TO FIND UID")
return
}
FirebaseManager.shared.firestore.collection("users").document(uid).getDocument { snapshot, error in
if let error = error {
self.errorMessage = "failed to fetch current user: \(error)"
print("failed to fetch current user: \(error)")
return
}
guard let data = snapshot?.data() else {
print("no data found")
self.errorMessage = "No data found"
return
}
self.user = .init(data: data)
}
}
}
TLDR: I can't figure out how to get my firestore map as a swiftui dictionary. Whenever I try to access my user's dictionary, the following error appears. If I force unwrap it crashes during runtime. I tried to coalesce with "??" but I don't know how to make it be the type it wants.
ForEach(vm.user?.socials.sorted(by: >) ?? [String:String], id: \.key) { key, value in
linkDisplay(social: key, handler: value)
.listRowSeparator(.hidden)
}.onDelete(perform: delete)
error to figure out
Please be patient. I have been looking for answers through SO and elsewhere for a long time. This is all new to me. Thanks in advance.
This is a two part answer; Part 1 addresses the question with a known set of socials (Github, Pinterest, etc). I included that to show how to map a Map to a Codable.
Part 2 is the answer (TL;DR, skip to Part 2) so the social can be mapped to a dictionary for varying socials.
Part 1:
Here's an abbreviated structure that will map the Firestore data to a codable object, including the social map field. It is specific to the 4 social fields listed.
struct SocialsCodable: Codable {
var Github: String
var Pinterest: String
var Soundcloud: String
var TikTok: String
}
struct UserWithMapCodable: Identifiable, Codable {
#DocumentID var id: String?
var socials: SocialsCodable? //socials is a `map` in Firestore
}
and the code to read that data
func readCodableUserWithMap() {
let docRef = self.db.collection("users").document("uid_0")
docRef.getDocument { (document, error) in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = document {
let user = try! doc.data(as: UserWithMapCodable.self)
print(user.socials) //the 4 socials from the SocialsCodable object
}
}
}
Part 2:
This is the answer that treats the socials map field as a dictionary
struct UserWithMapCodable: Identifiable, Codable {
#DocumentID var id: String?
var socials: [String: String]?
}
and then the code to map the Firestore data to the object
func readCodableUserWithMap() {
let docRef = self.db.collection("users").document("uid_0")
docRef.getDocument { (document, error) in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = document {
let user = try! doc.data(as: UserWithMapCodable.self)
if let mappedField = user.socials {
mappedField.forEach { print($0.key, $0.value) }
}
}
}
}
and the output for part 2
TikTok ogotok
Pinterest pintepogo
Github popgit
Soundcloud musssiiiccc
I may also suggest taking the socials out of the user document completely and store it as a separate collection
socials
some_uid
Github: popgit
Pinterest: pintepogo
another_uid
Github: git-er-done
TikTok: dancezone
That's pretty scaleable and allows for some cool queries: which users have TikTok for example.

How to know which initializer to use for reading data(Firebase)

I've got two initializers:
struct UserInfo{
let ref: DatabaseReference?
let key: String
let firstName: String
let lastName: String
let username: String
let pictureURL : String?
let admin : Bool
init(firstName: String, lastName:String,username:String,pictureURL:String?,admin:Bool, key:String = "" ){
self.ref = nil
self.key = key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = pictureURL
self.admin = admin
}
init?(snapshot:DataSnapshot){
guard let value = snapshot.value as? [String:AnyObject],
let firstName = value["firstName"] as? String,
let lastName = value["lastName"] as? String,
let username = value["userName"] as? String,
let profilePic = value["pictureURL"] as? String,
let admin = value["isAdmin"] as? Bool
else {return nil}
self.ref = snapshot.ref
self.key = snapshot.key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = profilePic
self.admin = admin
}
func toAnyObject()-> Any{
return [
"firstName": firstName,
"lastName": lastName,
"username": username,
"pictureURL":pictureURL as Any,
"isAdmin": admin
]
}
}
For reading most recent data I use this method combined with first init and it works:
let completed =
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
var newArray: [UserInfo] = []
if let dictionary = snapshot.value as? [String:Any]{
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let profilePic = dictionary["pictureURL"] as? String
let admin = dictionary["isAdmin"] as! Bool
let userInformation = UserInfo(firstName: firstName, lastName:
lastName, username: username,pictureURL: profilePic, admin: admin)
newArray.append(userInformation)
print(newArray)
completion(.success(newArray))
print(newArray)
}
Why and when do I need to use second init??
In Firebase tutorial on raywenderlich.com we gat example about: Synchronizing Data to the Table View using second init:
let completed = ref.observe(.value) { snapshot in
// 2
var newItems: [GroceryItem] = []
// 3
for child in snapshot.children {
// 4
if
let snapshot = child as? DataSnapshot,
let groceryItem = GroceryItem(snapshot: snapshot) {
newItems.append(groceryItem)
}
}
// 5
self.items = newItems
self.tableView.reloadData()
But my method works the same with first init.
The question is really asking about two things that functionally work the same.
In one case the snapshot is being "broken down" into its raw data (strings etc) within the firebase closure
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let userInformation = UserInfo(firstName: firstName, lastName: lastName...
and then passing that raw data to the struct. That object is then added to the array
In the second case the snapshot itself is passed to the struct
init?(snapshot:DataSnapshot) {
guard let value = snapshot.value as? [String:AnyObject],
and the snapshot is broken down into it's raw data within the object.
The both function the same.
It's a matter of readability and personal preference. Generally speaking having initializers etc within an object can make the code a bit more readable, the object more reusable and less code - see this pseudo code
DataObjects.infoRef.child(uid!).observe(.value){ snapshot, error in
let user = UserInfo(snapshot)
self.newArray.append(user)
})
That's pretty tight code.
Imagine if there were 10 places you wanted to access those objects within your app. In your first case, that code would have to be replicated 10 times - which could be a lot more troubleshooting. In my example above, the object itself does the heavy lifting so accessing them requires far less code.
Two other things. You may want to consider using .childSnapshot to access the data within a snapshot instead of a dictionary (either way works)
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
and please avoid force unwrapping optional vars
child(uid!)
as it will cause unstable code and random, unexplained crashes. This would be better
guard let uid = maybeUid else { return } //or handle the error

Updating collection in Firebase returns error "found nil while unwrapping optional value"?

I'm making this app where the idea is that you create a profile, add your dogs, and then update a timer on them (when they last ate, took a walk, etc). I'm having some issues with Firebase though. I managed to have the user add dogs to their account, but now that I'm trying to update some values on a certain dog the app crashes with a "Unexpectedly found nil while unwrapping an Optional value" which seems to be due to Firebase. My Database contains the user, their dogs and a collection of the dogs values, such as firstTimer. When I try to update this value with the setData() method it just keeps crashing and nothing shows in my database. i've also tried to update values individually but to no avail. Please tell me if I'm going about this the wrong way and if there's some other approach to try, thanks!
import Foundation
import Firebase
import UIKit
//DogViewController
class MyDogViewController: UIViewController {
var db: Firestore!
var auth: Auth!
var storage: Storage!
var thisDog: DogEntry?
var dogRef: DocumentReference!
override func viewDidLoad() {
thisDog?.firstTimer = (formattedDate)
if let dog = thisDog?.toAny() {
print("Let")
//THE PROGRAM PRINTS LET
dogRef.setData(dog)
//BUT CRASHES HERE
}
else {
print("Error")
}
}
}
}
//Dog Modal Class
class DogEntry {
var name: String
var image: String
var firstTimer: String
var secondTimer: String
var walking: Bool = false
var walkArray: [String]
var id: String = ""
init(name: String, image: String, firstTimer: String, secondTimer: String, walking: Bool, walkArray: [String]) {
self.name = name
self.image = image
self.firstTimer = firstTimer
self.secondTimer = secondTimer
self.walking = walking
self.walkArray = walkArray
}
init(snapshot: QueryDocumentSnapshot) {
let snapshotValue = snapshot.data() as [String : Any]
name = snapshotValue["name"] as! String
image = snapshotValue["image"] as! String
firstTimer = snapshotValue["firstTimer"] as! String
secondTimer = snapshotValue["secondTimer"] as! String
walking = snapshotValue["walking"] as! Bool
walkArray = snapshotValue["walkArray"] as! [String]
id = snapshot.documentID
}
func toAny() -> [String: Any] {
return ["name": name, "image": image, "firstTimer": firstTimer, "secondTimer": secondTimer, "walking": walking, "walkArray": walkArray]
}
}
Your dogRef is an implicitly unwrapped optional. You need to give it a value before you call it.