Updating child in firebase not working as expected - swift

I am trying to save data into firebase, by first generating a child using .childByAutoId() and then update the child with the necessary data. But it doesn't seem to work as expected.
The structure I am trying to achieve is
events
attendees
-L0P1D5arR0OkBf8h
userEmail: "user#user.com"
userName: "User name"
userPhone: "0864567182"
Here's what I have done so far:
guard let fee = events?["eventFee"] else {
return
}
guard let key = events?["eventKey"] else {
return
}
guard let eventTitle = events?["title"] else {
return
}
if fee == "0" {
var values = [String: String]()
self.ref = Database.database().reference()
let attendeekey = ref.child("events").child(key).child("attendees").childByAutoId().key
let userDetails = UserDetails()
for user in userDetails.currentUserDetails {
guard let userEmail = user.email else {
return
}
guard let firstName = user.firstName, let lastName = user.lastName else {
return
}
guard let userPhone = user.phoneNo else {
return
}
let userName = "\(firstName) \(lastName)"
values = ["userEmail": userEmail, "userName": userName, "userPhone": userPhone as! String]
}
ref.updateChildValues(["events/\(key)/attendees/\(attendeekey)": values], withCompletionBlock: {
(err, ref) in
if err != nil {
self.displayAlertMessage(message: err as! String, title: "Oops!")
//print(err ?? "An error occured")
return
}
let message = "You have successfully registered for \(eventTitle)"
self.displayAlertMessage(message: message, title: "Success!")
})
}
Is anything wrong with my approach?

Related

Fetched data from Firestore returns duplicates

I am trying to fetch data from firebase firestore. The problem i have is that my fetch is returning the results x4 times. For example when i do print(name) it print the users name x4 times.
I think there may be a loop that is not working correctly?
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
let data = document.data()
print(data)
let conversations: [Conversation] = data!.compactMap ({ dictionary in
guard let conversationId = data!["id"] as? String,
let name = data!["name"] as? String,
let otherUserUid = data!["other_user-uid"] as? String,
let latestMessage = data!["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
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
})
completion(.success(conversations))
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
Please note that ConversationRef.getDocument{..} will only Return One Specific Document, which you’re Referring here :
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document("jVymlfbpuAYQQ9Brf8SbUZ7KCGg1”)
So the let data = document.data()
will be single [String:Any] object(in this case Single ‘Conversation’),
not the Array of Dictionaries(eg: [Conversations]).
Try doing it this way:
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<Conversation, Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(
"jVymlfbpuAYQQ9Brf8SbUZ7KCGg1")
// get the otherUserUId TO DO
ConversationRef.getDocument { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
if let document = snapshot {
if document.exists {
if let data = document.data() {
if let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool {
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)
completion(.success(conversations))
}
}
}
else {
completion(.failure(DatabaseError.failedToFetch))
return
}
}
}
}
}
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations")
ConversationRef.addSnapshotListener { snapshot, error in
if error != nil {
print("Error connecting to database")
} else {
guard let snap = snapshot else {
completion(.failure(DatabaseError.failedToFetch))
return
}
for document in snap.documents {
let data = document.data()
print(data)
guard let conversationId = data["id"] as? String,
let name = data["name"] as? String,
let otherUserUid = data["other_user-uid"] as? String,
let latestMessage = data["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
}
print(name)
let latestMessageObject = LatestMessage(date: date, text: message, isRead: isRead)
let conversations = [Conversation(id: conversationId, name: name, otherUserUid: otherUserUid, latestMessage: latestMessageObject)]
completion(.success(conversations))
}
}
}
}

when i second time signup it clears realtime database from firebase in swift

When I signup second time from my app It allow me sign up but when I chek In data base my old acc was removed, I really need help in this please your help would be greatful for me.
Here is my Appdelegate func
var oldToken = ""
var dToken = ""
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Database.database().isPersistenceEnabled = true
let preferences = UserDefaults.standard
if preferences.object(forKey: "dtoken") != nil {
oldToken = preferences.object(forKey: "dtoken") as! String
}
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
dToken = uuid
print(uuid)
if oldToken != dToken && oldToken != "" {
preferences.set(dToken, forKey: "dtoken")
preferences.set(oldToken, forKey: "dtoken_old")
preferences.synchronize()
print("ohoh TOKEN CHANGED!!!")
//Change Tokens in DB.
}
}
return true
}
Here is My Signup Viewcontroller
func guidancefromcoach() {
guard let email = txtEmail.text else { return }
guard let pass = txtPassword.text else { return }
Auth.auth().createUser(withEmail: email, password: pass) { result, error in
if error != nil {
self.Alert(title: "Error", message: error!.localizedDescription)
}else {
if self.currentReachabilityStatus == .notReachable {
print("There is no internet connection")
self.retryAlert()
} else {
let preferences = UserDefaults.standard
preferences.set(dToken, forKey: "dtoken")
userIsCoach = false
preferences.set(userIsCoach, forKey: "userIsCoach")
authorizedCoachCode = self.txtEnterCoachId.text!
preferences.set(authorizedCoachCode, forKey: "authorizedCoachCode")
preferences.synchronize()
let userItem = UserStruct(dToken, self.txtFName.text!, self.txtLName.text!, self.txtEmail.text!, self.txtPhone.text!, coachCode, authorizedCoachCode, userIsCoach)
self.ref.child(dToken).setValue(userItem.toAnyObject())
print("dtoken......\(dToken)")
print("saved new user")
self.performSegue(withIdentifier: "fromSignupSegue", sender: Any?.self)
}
}
}
}
Here is my USERSTRUCT
struct UserStruct {
let ref: DatabaseReference?
var token = ""
var fname = ""
var lname = ""
var email = ""
var phone = ""
var coachCode = ""
var authorizedCoachCode = ""
var isCoach = false
init(_ token:String, _ fname:String, _ lname:String, _ email:String, _ phone:String, _ coachCode:String, _ authorizedCoachCode:String, _ isCoach:Bool) {
self.ref = nil
self.token = token
self.fname = fname
self.lname = lname
self.email = email
self.phone = phone
self.coachCode = coachCode
self.authorizedCoachCode = authorizedCoachCode
self.isCoach = isCoach
}
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: AnyObject],
let token = value["token"] as? String,
let fname = value["fname"] as? String,
let lname = value["lname"] as? String,
let email = value["email"] as? String,
let phone = value["phone"] as? String,
let coachCode = value["coachCode"] as? String,
let authorizedCoachCode = value["authorizedCoachCode"] as? String,
let isCoach = value["isCoach"] as? Bool else {
return nil
}
self.ref = snapshot.ref
//self.key = snapshot.key
self.token = token
self.fname = fname
self.lname = lname
self.email = email
self.phone = phone
self.coachCode = coachCode
self.authorizedCoachCode = authorizedCoachCode
self.isCoach = isCoach
}
func toAnyObject() -> Any {
return [
"token": token,
"fname": fname,
"lname": lname,
"email": email,
"phone": phone,
"coachCode": coachCode,
"authorizedCoachCode": authorizedCoachCode,
"isCoach": isCoach
]
}
Your help would be really greatful Thankyou in advance.
It seems that you're calling SetValue(), your old account isn't getting removed, it's getting set to different info. I'm not sure what the problem is but i suggest using this code:
Auth.auth().createUser(withEmail: email, password: pass) { (result, error) in
if err != nil {
//error code here
}
else {
guard let userID = Auth.auth().currentUser?.uid else { return }Firestore.firestore().collection("users").document(userID).setData(["email": email]) { (error) in
if error != nil {
//error happened
}
}
//Success, email has been saved to database
}
}
You can add all your other stuff to this code(userDefaults, etc.)
but this alone should be able to add a new document every time.

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.

Always getting nil in completion

I'm trying to get Map data I have in Firestore, this is how it looks:
I'm trying to get the data, and create an array of Friend Object and return the array in the completion handler.
This is what I have:
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend]?
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends?.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(nil)
}
}
}
I tried printing the results, and resultValue etc, they are not nil.
But, after trying to append and print the fetchedFriends Array, I get nil, and the completion is also nil.
I don't really understand why this is happening.
The problem is that you haven't initialized variable fetchedFriends and you have used optional type when appending data to it. Since it has not been initialized, it will skip appending to it. You should initialize it in the beginning. The updated code would be as follows.
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend] = []
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(nil)
}
}
}
Hope it helps.

How do I store values ​by referring to classes in FireStore

I'm creating a chat app using the MessageKit library in Swift 4.2 and FireStore.
My problem is that I can't store data acquired by real time communication using addSnapshotListener in the Message class and confirmed the contents of the class, but I do not think it is wrong.
messageListener is working properly. When handleDocumentChange is executed, it returns nil.
I checked the return value with document.data (), but the value returned.
How do I store values ​​by referring to classes?
guard var message = Message (document: change.document) else {
print ("return Message")
return
}
The data entered for FireStore is as follows
{
"channels": [{
"MOuL1sdbrnh0x1zGuXn7": { // channel id
"name": "Puppies",
"thread": [{
"3a6Fo5rrUcBqhUJcLsP0": { // message id
"content": "Wow, that's so cute!",
"created": "May 12, 2018 at 10:44:11 PM UTC-5",
"senderID": "YCrPJF3shzWSHagmr0Zl2WZFBgT2",
"senderUsername": "naturaln0va",
"recipientProfilePictureURL":"URL",
"recipientID":"ezample",
"recipientUsername" :"A"
"recipientProfilePictureURL":"aaaaa"
},
}]
},
}]
}
That's my message class:
class Message: MessageType {
var id: String?
var sentDate: Date
var kind: MessageKind
lazy var sender: Sender = Sender(id: atcSender.uid ?? "No Id", displayName: atcSender.uid ?? "No Name")
var atcSender: User
var recipient: User
var messageId: String {
return id ?? UUID().uuidString
}
var image: UIImage? = nil
var downloadURL: URL? = nil
let content: String
init(messageId: String, messageKind: MessageKind, createdAt: Date, atcSender: User, recipient: User) {
self.id = messageId
self.kind = messageKind
self.sentDate = createdAt
self.atcSender = atcSender
self.recipient = recipient
switch messageKind {
case .text(let text):
self.content = text
default:
self.content = ""
}
}
init(user: User, image: UIImage) {
self.image = image
content = ""
sentDate = Date()
id = nil
self.kind = MessageKind.text("xxx")
self.atcSender = user
self.recipient = user
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let sentDate = data["created"] as? Date else {
return nil
}
guard let senderID = data["senderID"] as? String else {
return nil
}
guard let senderUsername = data["senderUsername"] as? String else {
return nil
}
guard let senderProfilePictureURL = data["senderProfilePictureURL"] as? String else {
return nil
}
guard let recipientID = data["recipientID"] as? String else {
return nil
}
guard let recipientUsername = data["recipientUsername"] as? String else {
return nil
}
guard let recipientProfilePictureURL = data["recipientProfilePictureURL"] as? String else {
return nil
}
id = document.documentID
self.sentDate = sentDate
self.atcSender = User(uid: senderID, username: senderUsername, firstname: "", lastname: "", email: "", profileUrl: senderProfilePictureURL)
self.recipient = User(uid: recipientID, username: recipientUsername, firstname: "", lastname: "", email: "", profileUrl: recipientProfilePictureURL)
if let content = data["content"] as? String {
self.content = content
downloadURL = nil
} else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
downloadURL = url
self.content = ""
} else {
return nil
}
self.kind = MessageKind.text(content)
}
required init(jsonDict: [String: Any]) {
fatalError()
}
var description: String {
return self.messageText
}
var messageText: String {
switch kind {
case .text(let text):
return text
default:
return ""
}
}
var channelId: String {
let id1 = (recipient.username ?? "")
let id2 = (atcSender.username ?? "")
return id1 < id2 ? id1 + id2 : id2 + id1
}
}
extension Message: DatabaseRepresentation {
var representation: [String : Any] {
var rep: [String : Any] = [
"created": sentDate,
"senderID": atcSender.uid ?? "",
"senderUsername": atcSender.username ?? "",
"senderProfilePictureURL": atcSender.profileUrl ?? "",
"recipientID": recipient.uid ?? "",
"recipientUsername": recipient.username ?? "",
"recipientProfilePictureURL": recipient.profileUrl ?? "",
]
if let url = downloadURL {
rep["url"] = url.absoluteString
} else {
rep["content"] = content
}
return rep
}}extension Message: Comparable {
static func == (lhs: Message, rhs: Message) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: Message, rhs: Message) -> Bool {
return lhs.sentDate < rhs.sentDate
}
}
ChatViewController:
import UIKit
import MessageKit
import MessageInputBar
import Firebase
import FirebaseFirestore
import FirebaseAuth
class ChatViewController: MessagesViewController {
private let db = Firestore.firestore()
private var reference: CollectionReference?
private var messages: [Message] = []
private var messageListener: ListenerRegistration?
private let user: User
private let channel: Channel
let uid = Auth.auth().currentUser?.uid
init(user: User, channel: Channel) {
self.user = user
self.channel = channel
super.init(nibName: nil, bundle: nil)
title = channel.name
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
messageListener?.remove()
}
override func viewDidLoad() {
super.viewDidLoad()
guard let id = channel.id else {
navigationController?.popViewController(animated: true)
return
}
reference = db.collection(["channels", id, "thread"].joined(separator: "/"))
// reference?.addSnapshotListener { querySnapshot, error in
// guard let snapshot = querySnapshot else {
// print("Error fetching snapshots: \(error!)")
// return
// }
// snapshot.documentChanges.forEach { diff in
// if (diff.type == .added) {
// print("New city: \(diff.document.data())")
// }
// if (diff.type == .modified) {
// print("Modified city: \(diff.document.data())")
// }
// if (diff.type == .removed) {
// print("Removed city: \(diff.document.data())")
// }
// }
// }
messageListener = reference?.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
print("handleDocumentChange")
}
}
self.navigationItem.title = title
messageInputBar.delegate = self
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
messageInputBar.sendButton.tintColor = UIColor.lightGray
//scrollsToBottomOnKeyboardBeginsEditing = true // default false
//maintainPositionOnKeyboardFrameChanged = true // default false
}
private func save(_ message: Message) {
reference?.addDocument(data: message.representation) { error in
if let e = error {
print("Error sending message: \(e.localizedDescription)")
return
}
self.messagesCollectionView.scrollToBottom()
}
}
private func insertNewMessage(_ message: Message) {
guard !messages.contains(message) else {
return
}
messages.append(message)
messages.sort()
let isLatestMessage = messages.index(of: message) == (messages.count - 1)
let shouldScrollToBottom = messagesCollectionView.isAtBottom && isLatestMessage
messagesCollectionView.reloadData()
if shouldScrollToBottom {
DispatchQueue.main.async {
self.messagesCollectionView.scrollToBottom(animated: true)
}
}
}
private func handleDocumentChange(_ change: DocumentChange) {
guard var message = Message(document: change.document) else {
print("return Message")
return
}
switch change.type {
case .added:
print("add Message")
insertNewMessage(message)
default:
break
}
}
}
Console prints
New city: ["senderUsername": panyayan,
"senderID": RAMIqHAVeoU4TKkm3FDw7XUwgym2,
"created": FIRTimestamp:seconds=1544623185 nanoseconds=412169933>,
"recipientUsername": panyayan,
"content": AAA,
"recipientID": RAMIqHAVeoU4TKkm3FDw7XUwgym2,
"recipientProfilePictureURL": https:,
"senderProfilePictureURL": https:]
return Message
handleDocumentChange
Dont know if you still need it but:
The document.data() field created is a FIRTimestamp.
When you try to init the Message object you use
guard let sentDate = data["created"] as? Date else {
return nil
}
Thats might be a reason, why your object is nil.
Try something like
guard let sentTimestamp = data["created"] as? Timestamp else {
return nil
}
...
self.sentDate = sentTimestamp.dateValue()