I'm working on the firebase chat app and here is the first function insertConversation2 and this function creating a node of conversation array that contains all the data about chat.
Now I want when user sending the message and code goes to success block, and at this point, I want to create another node of messages through function and pass the first function
completion handler to the second function (finishCreatingConversation) completion handler but the issue is that it's not working fine and not creating the second node. See the firebase screenshot conversation node is creating but message-id not creating please check the code thanks.
First function
func insertConversation2(with otherUserEmail: String,name:String,message:Message,completion:#escaping(Bool) -> Void){
let dformatter = DateFormatter()
dformatter.dateFormat = "dd/MM/yyy HH:mm"
let dateToString = dformatter.string(from: Date())
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
return}
var getSafeEmail = getUserEmail(currentEmail: email)
database.child("\(getSafeEmail)").observeSingleEvent(of: .value) { (snapshot) in
//if user is not preens to go to false block else go to furhter
guard var userNode = snapshot.value as? [String:Any] else {
completion(false)
print("user not found in insert time in networking manager")
return
}
var messageData = ""
switch message.kind{
case .text(let messageText):
messageData = messageText
default:break
}
let conversationId = "conversation_\(message.messageId)"
let newConversation:[String:Any] = [
"id":conversationId,
"other_user_email": otherUserEmail,
"name":name,
"latest_message":[
"date":dateToString,
"message":messageData,
"is_read":false
]
]
if var userConversation = userNode["conversation"] as? [[String:Any]]{
//conversation array is exiten append the conversation data
userConversation.append(newConversation)
userNode["conversation"] = userConversation
database.child("\(getSafeEmail)").setValue(userNode) { (error, ref) in
guard error != nil else {return}
self.finishCreatingConversation(conversationId: conversationId, message: message, completion: completion)
//completion(true)
}
}
else{
userNode["conversation"] = [
newConversation
]
database.child("\(getSafeEmail)").setValue(userNode) { (error, ref) in
guard error != nil else {return}
self.finishCreatingConversation(conversationId: conversationId, message: message, completion: completion) //second not working fine
//completion(true) //passing a refrence of completion in above function of insert method
}
}
}
}
Second function
func finishCreatingConversation(conversationId:String,message:Message,completion:#escaping(Bool) -> Void){
var messageData = ""
switch message.kind{
case .text(let messageText):
messageData = messageText
}
let dformatter = DateFormatter()
dformatter.dateFormat = "dd/MM/yyy HH:mm"
let dateToString = dformatter.string(from: Date())
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
return}
//var getUserEmail = getUserEmail(currentEmail: email)
let getUserEmailData = getUserEmail(currentEmail: email)
let collectionMessge: [String:Any] = [
"id":message.messageId,
"type":message.kind.messageKindString,
"content":messageData,
"date": dateToString,
"sender_email":getUserEmailData,
"is_read":false
]
database.child("\(conversationId)").setValue(collectionMessge) { (error, ref) in
guard error != nil else {return}
completion(true)
}
}
Firebase Image:
Replace
self.finishCreatingConversation(conversationId: conversationId, message: message, completion: completion)
with
self.finishCreatingConversation(conversationId: conversationId, message: message) { res in
completion(res)
}
Related
I'm using the message Kit. In a chatDashbaord extension I've boolean condition like if the isnewConversation contain true then call the function insertConversation2 and if isnewConversation contain false then call the function sendMessag.But I've checked using the break points the code is not going to isNewconversation block and I don't know why it's happening.When I'm not using the boolean statement the code working file and append the data into an array please check the code thanks.
MessageViewController:
class ChatDashboard: MessagesViewController {
var userActive: String? = nil
var receiverName:String? = nil
var isnewConversation = false
var recevierName:String? = nil
var otheruserEmail: String? = nil
var conversationId:String? = nil
var messages : [Message] = []{
didSet{
DispatchQueue.main.async {
self.messagesCollectionView.reloadDataAndKeepOffset()
}
}
extension ChatDashboard:
extension ChatDashboard: InputBarAccessoryViewDelegate{
func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
guard !text.replacingOccurrences(of: "", with: "").isEmpty,let selfSender =
self.selfSender,let messageId = createMessageId() else{
return
}
let message = Message(sender: selfSender, messageId: messageId, sentDate: Date(), kind: .text(text))
guard let otheruserEmail = otheruserEmail,let recevierName = recevierName else {return}
if isnewConversation{
NetworkingService.shared.insertConversation2(with:otheruserEmail,name:recevierName, message: message) { (success) in
if success{
print("message Send")
self.isnewConversation = false
}
else{
print("not send")
}
}
}
else{
guard let converId = conversationId else {return}
NetworkingService.shared.sendMessage(converId,recevierName,message) { (success) in
if success{
print("message send in")
}
else{
print("message not send")
}
}
}
}
}
}
SendMessage func code when condtion false:
func sendMessage(_ convesationId:String,_ name:String,_ message:Message,completion:#escaping(Bool) -> Void){
self.sendMessageReciverToSender(convesationId) { (user) in
guard var receiverToSenderNdoe = user else {completion(false); return}
let dformatter = DateFormatter()
dformatter.dateFormat = "dd/MM/yyy HH:mm"
let dateToString = dformatter.string(from: Date())
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
completion(false)
return
}
let currentUserEmail = getUserEmail(currentEmail: email)
var messageData = ""
switch message.kind{
case .text(let messageText):
messageData = messageText
default:
break
}
let newMessge: [String:Any] = [
"id":message.messageId,
"type":message.kind.messageKindString,
"content":messageData,
"date": dateToString,
"sender_email":currentUserEmail,
"is_read":false,
"name":name
]
//issues is here
if var conversationData = receiverToSenderNdoe["messages"] as? [[String:Any]]{
conversationData.append(newMessge)
receiverToSenderNdoe["messages"] = conversationData
self.reciverToSenderSave(convesationId, newMessge) { (result) in
if result{
completion(true)
}
else{
completion(false)
}
}
}
}
}
Inside the sendMessage func this two function called
func sendMessageReciverToSender(_ conversationId:String,completion:#escaping([[String:Any]]?) -> Void){
database.child("\(conversationId)/messages").observeSingleEvent(of: .value) { (snapshot) in
if let currentUser = snapshot.value as? [[String:Any]]{
completion(currentUser)
}
else{
print("errro in reciver to sender sendMessageReciverToSender func ")
completion(nil)
}
}
}
func reciverToSenderSave(_ conversationId:String,_ conversation:[[String:Any]],completion:#escaping(Bool)-> Void){
database.child("\(conversationId)/messages").setValue(conversation){(error,ref) in
if error == nil{
completion(true)
}
else{
print("eroor in receiptin \(error?.localizedDescription)")
completion(false)
}
}
}
Your sendMessage has return without calling completion here
guard var receiverToSenderNdoe = user else {return}
Should be
guard var receiverToSenderNdoe = user else { completion(false) ; return }
And here
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
return
}
Should be
guard let email = UserDefaults.standard.value(forKey: "useremail") as? String else {
print("Email not found in user default")
completion(false)
return
}
You have to make sure all paths are handled so to have your callback called in success/failure , BTW something like this
if result{
completion(true)
}
else{
completion(false)
}
Could be shortly
completion(result)
In my page named service, xcode points to the user and gives an error. but it doesn't work. What do you think should I change?
my user is already optional. I think it is an index problem but I don't know how to solve it I would appreciate it if you could help.where do you think the problem
message.swift
import Firebase
struct Message {
let text: String
let toId: String
let fromId: String
var timestamp: Timestamp!
var user: User?
let isFromCurrentUser :Bool
init(dictionary: [String: Any]) {
self.text = dictionary["text"] as? String ?? ""
self.toId = dictionary["toId"] as? String ?? ""
self.fromId = dictionary["fromId"] as? String ?? ""
self.timestamp = dictionary["timestamp"] as? Timestamp ?? Timestamp(date: Date())
self.isFromCurrentUser = fromId == Auth.auth().currentUser?.uid
}
}
struct Conversation {
let user: User
let message : Message
}
Service.Swift
import Firebase
struct Service {
static func fetchUsers (completion: #escaping([User]) -> Void) {
var users = [User] ()
COLLECTION_USERS.getDocuments { (snapshot, error) in
snapshot?.documents.forEach({ (document) in
let dictionary = document.data()
let user = User(dictionary: dictionary)
users.append(user)
completion(users)
})
}
}
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
static func fetchConversations (completion: #escaping([Conversation]) ->Void) {
var conversations = [Conversation]()
guard let uid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(uid).collection("recent-messages").order(by: "timestamp")
query.addSnapshotListener { (snapshot, error) in
snapshot?.documentChanges.forEach({ change in
let dictionary = change.document.data()
let message = Message(dictionary: dictionary)
self.fetchUser(widhtUid: message.toId) { user in
let conversation = Conversation(user:user, message: message)
conversations.append(conversation)
completion(conversations)
}
})
}
}
static func fetchMessages (forUser user: User, completion: #escaping([Message])-> Void) {
var messages = [Message]()
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(currentUid).collection(user.uid).order(by: "timestamp")
query.addSnapshotListener{(snapshot,error) in
snapshot?.documentChanges.forEach({ change in
if change.type == .added {
let dictionary = change.document.data ()
messages.append(Message(dictionary: dictionary))
completion(messages)
}
})
}
}
static func uploadMessage(message: String, to user: User, completion: ((Error?)->Void)?) {
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let data = ["text": message,
"fromId": currentUid,
"toId": user.uid,
"timestamp" : Timestamp(date: Date())] as [String : Any]
COLLECTION_MESSAGES.document(currentUid).collection(user.uid).addDocument(data:data) { _ in
COLLECTION_MESSAGES.document(user.uid).collection(currentUid).addDocument(data:data,completion:completion)
COLLECTION_MESSAGES.document(currentUid).collection("recent- messages").document(user.uid).setData(data)
COLLECTION_MESSAGES.document(user.uid).collection("recent- messages").document(currentUid).setData(data)
}
}
}
In this method:
static func fetchUser(widhtUid uid: String, completion:#escaping ([User]) -> Void)
The completion closure's parameter should be a User, not an array of users - [User].
Xcode should point you to the line where this error happens...
Anyway, here
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
Your completion:#escaping([User]) ->Void) expects an array [User] , but you invoke it with just one User object here completion(user)
I have a bit of a lengthy question, So I apologize in advance I will try to illustrate this to the best of my abilities. I am trying to establish a notifications view controller that calls different types of data from Firebase and sets different notification types.
In the image above, this is how the cells should look when a user sends a notification to firebase. The user associated with that specific notification type as called and posted onto the screen.
In the firebase structure, We see that all of the information Stored is saved under the UID of the user in the first picture and is set under that specific users notification to show who is sending them a notification which is correct. These users names and images show perfectly as well as the image on the right.
The code I use to save this information is below,
fileprivate func saveSwipeToDataBase(didLike: Any) {
let swipeDate = Int(NSDate().timeIntervalSince1970)
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let cardUID = topCardView?.cardViewModel.uid else { return }
let documentData = ["workerId": uid,
"didLike": didLike,
"checked": 0,
"Swipe Date": swipeDate,
"type": SWIPE_INT_VALUE,
"posterId" : cardUID] as [String : Any]
self.postJobNotificationsIntoDatabseWithUID(uid: cardUID, values: documentData as [String : AnyObject])
}
private func postJobNotificationsIntoDatabseWithUID(uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference(fromURL: "https://oddjobs-b131f.firebaseio.com/")
let usersReference = ref.child("notifications").child(uid).childByAutoId()
usersReference.setValue(values, withCompletionBlock: { (err, ref) in
if err != nil {
print("error saving data into firebase")
return
}
})
}
And below is how I retrieve this information and store it onto the Notifications View controller.
func fetchNotifications() {
guard let currentUID = Auth.auth().currentUser?.uid else { return }
NOTIFICATIONS_REF.child(currentUID).observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
print(dictionary)
for (_, postingRawData) in dictionary {
guard let postingDictionary = postingRawData as? Dictionary<String, AnyObject> else { continue }
guard let uid = postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["posterId"] as? String {
Database.fetchPoster(with: postId, completion: {(poster) in
let notification = userNotifications(user: user, poster: poster, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
})
} else {
let notification = userNotifications(user: user, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
}
})
}
}
}
Now that I got the correct way to setup up and show out of the way, I will show my enum and how I am distinguishing the different types of calls from firebase.
class userNotifications {
// MARK: - establish notificationTypes
enum NotificationType: Int, Printable {
case swipe
case accepted
case confirmed
case completed
case pay
var description: String {
switch self {
case .swipe: return " swiped on your Job "
case .accepted: return " accepted you to complete the job, "
case .confirmed: return " confirmed the job"
case .completed: return " completed the job"
case .pay: return " pay for completed"
}
}
init(index: Int) {
switch index {
case 0: self = .swipe
case 1: self = .accepted
case 2: self = .confirmed
case 3: self = .completed
case 4: self = .pay
default: self = .swipe
}
}
}
// MARK: - access firebaseData
var creationDate: Date!
var timeDate: Date!
var uid: String!
var fromId: String?
var workerId: String?
var user: User!
var poster: Poster!
var type: Int?
var notificationType: NotificationType!
var didCheck = false
init(user: User? = nil, poster: Poster? = nil, dictionary: Dictionary<String, AnyObject>) {
self.user = user
if let poster = poster {
self.poster = poster
}
if let swipeDate = dictionary["Swipe Date"] as? Double {
self.creationDate = Date(timeIntervalSince1970: swipeDate)
}
if let createDate = dictionary["creationDate"] as? Double {
self.creationDate = Date(timeIntervalSince1970: createDate)
}
if let swipeDate = dictionary["time&date"] as? Double {
self.timeDate = Date(timeIntervalSince1970: swipeDate)
}
if let type = dictionary["type"] as? Int {
self.notificationType = NotificationType(index: type)
}
if let uid = dictionary["uid"] as? String {
self.uid = uid
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let workerId = dictionary["workerUID"] as? String {
self.workerId = workerId
}
if let checked = dictionary["checked"] as? Int {
if checked == 0 {
self.didCheck = false
} else {
self.didCheck = true
}
}
}
}
Above is the different types of notifications to be set.
Now, My issue is If I call a different notification type, such as .accepted, the information calls in a very different way.
The image above seems correct, However, the name and image are incorrect. it should be from the user ZacheryWilcox instead of Cjbwjdhbe. the user Cjbwjdhbe is the current user and the user who should be receing a notification from Zacherywilcox. not from itself.
In firebase, the information is saved as
the code I use to save this information is below
var workerUser: User? {
didSet {
let name = workerUser?.name
workerNameLabel.text = name
let workersUID = workerUser?.uid
workerNameLabel.text = name
guard let profileImage = workerUser?.profileImageUrl else { return }
workerImageView.loadImageUsingCacheWithUrlString(profileImage)
}
}
func saveUserData() {
let workUser = self.workerUser
guard let uid = Auth.auth().currentUser?.uid else { return }
let workerId = workUser?.uid
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
let user = User(dictionary: dictionary as [String : AnyObject])
workUser?.uid = snapshot.key
self.datePicker.datePickerMode = UIDatePicker.Mode.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd yyyy/ hh:mm a"
let selectedDate = dateFormatter.string(from: self.datePicker.date)
let creationDate = Int(NSDate().timeIntervalSince1970)
print(selectedDate)
let docData: [String: Any] = [
"workerId": workerId!,
"time&date": selectedDate,
"posterId" : uid,
"creationDate": creationDate,
"location": user.address!,
"type": 1,
"jobPost": "someUIDString",
"checked": 0,
]
self.postJobNotificationsIntoDatabseWithUID(uid: workerId!, values: docData as [String : AnyObject])
}, withCancel: { (err) in
print("attempting to load information")
})
print("Finished saving user info")
self.dismiss(animated: true, completion: {
print("Dismissal complete")
})
}
private func postJobNotificationsIntoDatabseWithUID(uid: String, values: [String: AnyObject]) {
let ref = Database.database().reference(fromURL: "https://oddjobs-b131f.firebaseio.com/")
let usersReference = ref.child("notifications").child(uid).childByAutoId()
usersReference.setValue(values, withCompletionBlock: { (err, ref) in
if err != nil {
print("error saving data into firebase")
return
}
})
}
When the type .accepted is being used to differentiate what notificationType is being called, the user who sent the notification is not being set correctly and I have no idea what is the reasoning behind this. The correct user that is sending this information over is Zacherywilcox, and that users image and name should be set to the user's notification screen. not the user Cjbe... I was wondering if anyone could help me fix these issues. Thank you in advance. I'm starting to think that the way I am saving the users information when accepting the user is incorrect.
When I am fetchingNotifications(), is it possible that since calling
guard let uid = postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["posterId"] as? String {
has an effect on whats going on? if so, Is there a way to differentiate between what notificationType is being called and fetch what notifications has been called with their respective users?
Just update your code to:
func fetchNotifications() {
guard let currentUID = Auth.auth().currentUser?.uid else { return }
NOTIFICATIONS_REF.child(currentUID).observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
print(dictionary)
let notificationId = snapshot.key
for (_, postingRawData) in dictionary {
guard let postingDictionary = postingRawData as? Dictionary<String, AnyObject> else { continue }
guard let type = postingDictionary["type"] as? Int else { continue }
guard let uid = (type == userNotifications.NotificationType.accepted.rawValue) ? postingDictionary["fromId"] as? String : postingDictionary["workerId"] as? String else { continue }
Database.fetchUser(with: uid, completion: { (user) in
if let postId = postingDictionary["fromId"] as? String {
Database.fetchPoster(with: postId, completion: {(poster) in
let notification = userNotifications(user: user, poster: poster, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
})
} else {
let notification = userNotifications(user: user, dictionary: postingDictionary)
self.notifications.append(notification)
self.handleSortNotification()
}
// NOTIFICATIONS_REF.child(currentUID).child(notificationId).child("checked").setValue(1)
})
}
}
}
This will solve your problem.
I really want to know why the data is overwritten when the user types new data,
I want it to add more data to it not overwrite it the data
Also want to know how to read it
Thank you in advance
let oDB = Database.database().reference().child("Data")
let oDictionary = ["Data1" : strange.text! , "Data2" : stranger.text!]
let uid = Auth.auth().currentUser?.uid
oDB.child(uid!).setValue(oDictionary) {
(error, reference) in
if error != nil{
print(error!)
} else {
print("saved Sucessfully")
self.navigationController?.popViewController(animated: true)
}
}
//In another ViewController
func updateRequest() {
let uid = Auth.auth().currentUser?.uid
let yDb = Database.database().reference().child("Data").child(uid!)
postDb.observeSingleEvent(of: .value) { (snapShot) in
if let snapShotValue = snapShot.value as? Dictionary<String, String> {
let text = snapShotValue["Data1"]!
let case = snapShotValue["Data2"]!
let data = Data()
data.s= text
data.y = case
self.array.append(data)
self.table.reloadData()
}
}
}
setValue overwrites the old content , You may need childByAutoId
oDB.child(uid!).childByAutoId().setValue(oDictionary) {
(error, reference) in
if error != nil{
print(error!)
} else {
print("saved Sucessfully")
self.navigationController?.popViewController(animated: true)
}
This will give this structure
Data
> uid
> someKey1 <<<< auto generated
Data1:"---"
Data2:"---"
> someKey2 <<<< auto generated
Data1:"---"
Data2:"---"
Read
//In another ViewController
func updateRequest() {
let uid = Auth.auth().currentUser?.uid
let yDb = Database.database().reference().child("Data").child(uid!)
postDb.observeSingleEvent(of: .value) { (snapShot) in
if let snapShotValue = snapShot.value as? [String:[String:String]] {
Array(snapShotValue.values).forEach {
let data = Data()
data.s= $0["Data1"]!
data.y = $0["Data2"]!
self.array.append(data)
}
self.table.reloadData()
}
}
}
I am using firebase for chat app.when I try to send a message. than message, table got value but not update chat-message table. I refer to this link Here [https://github.com/DevSurya/ChatApp-Swift-And-Firebase].
when I run demo its working fine. Observe method is not calling in my case and collectionview is not refreshing
This is how I write data :
private func sendMessageWithProperty(_ property: [String: AnyObject]){
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.id!
let fromId = Auth.auth().currentUser!.uid
let timeStamp = NSNumber.init(value: Date().timeIntervalSince1970)
var values: [String : AnyObject] = ["toId":toId as AnyObject, "fromId":fromId as AnyObject, "timeStamp":timeStamp]
values = values.merged(with: property)
childRef.updateChildValues(values)
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error!)
return
}
self.inputTextField.text = nil
let userMessageRef = Database.database().reference().child("user-messages").child(fromId).child(toId)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId: 1])
let recipentUserMessageRef = Database.database().reference().child("user-messages").child(toId).child(fromId)
recipentUserMessageRef.updateChildValues([messageId: 1])
}
}
This is how I read data
func observeMessage() {
guard let uid = Auth.auth().currentUser?.uid, let toId = user?.id else {
return
}
let userMessageRef = Database.database().reference().child("user-messages").child(uid).child(toId)
userMessageRef.observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageId)
messagesRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: AnyObject] else {
return
}
let message = Message()
message.setValuesForKeys(dict)
self.messages.append(message)
DispatchQueue.main.async {
self.collectionView?.reloadData()
let indexpath = NSIndexPath.init(item: self.messages.count-1, section: 0)
self.collectionView?.scrollToItem(at: indexpath as IndexPath, at: .bottom, animated: true)
}
}, withCancel: nil)
}, withCancel: nil)
}
The ObserveMessage() function get automatically called when there is an update in database.but in my case its not get called. I think the problem is with user-messages table: it is not created when I send message.