how to get latest message from firebase database in ios - ios

I have don't know what the problem please help me. When I get a particular message from firebase database then the value is getting but my app got a crash on one line.So please tell me what I do wrong in my code.below is my function.We also provide the screenshot of the error.
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
print("getModelFromFirebase")
var message:String=""
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
if value?["message"] as? String != ""
{
DispatchQueue.main.async
{
message = (value?["message"] as? String)! //My app stop on this line
completionmessage(message)
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
func callAPI()
{
let response = (jsonResult.object(forKey: "chatListArr") as? NSArray)!
if response.count > 0
{
for i in 0..<response.count
{
let dict = response[i] as! NSDictionary
let chatlist = ChatList(dict: dict)
self.arr_list.append(chatlist)
}
for i in 0..<self.arr_list.count
{
let chatlist = self.arr_list[i]
self.getLatestMessageFromFirebase(token: chatlist.token, completionmessage: { (message) in
self.arr_list[i].msg = message
})
}
self.table_view.reloadData()
}
}
Please help me.
Thanks in Advance.

First of all you should clean your code up a bit, you do a couple of things which would be considered anti patterns
func getLatestMessageFromFirebase(token:String,completionmessage: #escaping (_ message:String) -> Swift.Void)
{
ref.child("chatmessage/devicetoken/").child(token).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for snap in snapshot.children.allObjects as [DataSnapshot] {
let value = snap.value as? [String: Any] ?? [:] // A good way to unwrap optionals in a single line
if let message = value["message"] as? String {
DispatchQueue.main.async {
completionmessage(message)
}
}
}
})
{ (error) in
print(error.localizedDescription)
}
}
With the above code your app shouldnt crash. And if there is a message AND it is a string (which might have been your problem before) then your callback will fire.

Related

Swift future publisher error when getting data from firebase

I'm new to swift language, but now I'm developing app with swift and firebase.
I'm using realtime database and while making two identical functions for getting data one is working and one is not.
This is the working one:
func getRoom(admin: String) -> AnyPublisher<Room, Error> {
Deferred {
Future { promise in
Database.database().reference().child("rooms/\(admin)")
.getData { [self] error, snapshot in
guard error == nil else {
promise(.failure(error!))
print(error!.localizedDescription)
return;
}
let value = snapshot.value as? [String:[String:Any]]
for (key, room) in value ?? [:] {
if (key == admin){
let admin = room["admin"] as? [String:Any]
let subjects = room["subjects"] as? [String]
let difficulties = room["difficulties"] as? [String]
let users = room["users"] as? [[String:Any]]
let questions = room["questions"] as? [[String:Any]]
let isGameStarted = room["is_game_started"] as? String
let room = Room(admin: dictionaryToUser(userDict: admin!), subjects: subjects!, difficutlies: difficulties!, users: dictionaryToUsersArr(usersArrDict: users!), questions: dictionaryToQuestionArr(questionsArrDict: questions!), is_game_started: isGameStarted!)
promise(.success(room))
}
}
}
}
}
.eraseToAnyPublisher()
}
And this is not working:
func getRoomUsersFromRoom(admin: String) -> AnyPublisher<[RoomUser], Error> {
var roomUsers: [RoomUser] = []
Deferred {
Future { promise in
Database.database().reference()
.child("rooms/\(admin)")
.getData { error, snapshot in
guard error == nil else {
promise(.failure(error!))
print(error!.localizedDescription)
return;
}
let value = snapshot.value as? [String:[String:Any]]
for (key, room) in value ?? [:] {
if (key == admin){
let users = room["users"] as? [[String:Any]]
for i in 0..<users!.count {
roomUsers.append(RoomUser(username: users![i]["username"] as! String, gamePoints: users![i]["room_points"] as! Int))
}
promise(.success(roomUsers))
}
}
promise(.success(roomUsers))
}
}
}
.eraseToAnyPublisher()
}
The errors in the second one are on the line with the Future, telling:
"Generic parameter 'Failure' could not be inferred"
"Generic parameter 'Output' could not be inferred"
It suggests me to put Future<Any, Error> and the error is gone but then I have warning on "eraseToAnyPublisher()", which I think is not good.
What is difference between the functions and any ideas how to solve this?
Thanks in advance

Completion Block Error Generated when accessing User account in Firebase

I am attempting to create a completion block while pulling a users profile from a firebase table. I need it to complete before I allow it to pass back a value.
Here is what I have so far:
func getProf(email: String, pass: String, completionBlock: #escaping (_ success: Bool) -> (Int)) {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
self.zDaily = value?["qday"] as? Int ?? 0
}) {
if let error = error {
completionBlock(false)
} else {
completionBlock(true)
return zDaily
}
}
}
I'm getting the following error:
Cannot convert value of type '() -> _' to expected argument type '((Error) -> Void)?'
I'm not sure how to fix this, any suggestion would be appreciated.
I'm not sure if that'll fix the error. If it doesn't, then I think I know the issue and it would be with your error block and your observeEvent.
Edit: Just made a change to return an error object from the observeEvent.
func getProf(email: String, pass: String, completionBlock: #escaping (Bool, Int) -> ()) {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
self.zDaily = value?["qday"] as? Int ?? 0
}) { (error) in //Added return error value - this may fix your error
if let error = error {
completionBlock(false, 0) //Add default 0 to return if error
} else {
completionBlock(true, zDaily) //Added zDaily as Int to return
//return zDaily - return will not return in async function so you can return the value in completionBlock above.
}
}
}

Load new messages Swift 4.2 & Firebase

I have created a messaging system for my app and am paginating the messages within the chat log but I'm having an issue that if a new message is sent the user will have to leave the screen and re open the controller to view the new messages they have sent/received. I have tried to reload the collection view and observe the messages again with no luck. Any help is appreciated.
Observing the messages. With Pagination. (working great! On initial load.)
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 4).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
From Firebase database documentation
In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario: the event callback added triggers once and then does not trigger again.
I suggest you to change to observeEventType:withBlock whichs allow you to observe all changes events.
Hope this helps.
The way I set mine up was to call the function in viewDidLoad and then again in viewDidAppear. I'm still learning as well, but you may want to try that, it would probably look something like this:
override func viewDidLoad() {
super.viewDidLoad()
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
And again in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}

Appending to an array in Firebase - Asynchronous and reloadData()

I am having trouble with the asychronous nature of Firebase, particulary appending to an array from within an observe function.
Any suggestions or help would be much appreciated :)
The comp that is appended to the users array in the selectUsersComp function disappears when the firebase observe function is exited, even though I reload the data in the collectionView.
I have tried using Dispatch.main.async but it has not helped. I have a Firebase observe function inside another Firebase observe function. Does this change things?
fileprivate func fetchStartedComps() {
let ref = Database.database().reference().child("startedComps")
ref.queryOrdered(byChild: "creationDate").observe(.value, with: {
(snapshot) in
guard let dictionaries = snapshot.value as? [String : Any] else { return }
dictionaries.forEach({ (key, value) in
guard let compDictionary = value as? [String: Any] else { return }
let comp = StartedComp(Id: key, dictionary: compDictionary)
self.selectUsersComp(comp: comp)
})
self.filteredStartedComps = self.startedComps
self.collectionView?.reloadData()
}) { (err) in
print("Failed to fetch comps for search", err)
}
}
func selectUsersComp(comp: StartedComp) {
guard let userId = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("startedComps").child(comp.title).child("invitedUsers")
ref.observe(.value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String : Any] else { return }
dictionaries.forEach({ (key, value) in
if key == userId {
self.startedComps.append(comp)
}
})
self.collectionView?.reloadData()
}) { (err) in
print(err)
}
}

How to create a function that inserts data into a block

First can someone help me come up with a better title? I just don't know the correct terminology on this one.
Here is my code,
func loadPublicFeed() {
ref = FIRDatabase.database().reference()
ref.child("brackets").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key as String
let post = BracketsPublicFeed(postKey:key, postData: bracketsDict)
self.posts.insert(post, at: 0)
}
}
}
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
})
}
download data then do stuff. I want to take this function refactor it so I can just call the refactored function and add these
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
into it.
It might look something like this,
func loadFeedTest() {
fetchTest.loadPublicFeed(collectionView: self.collectionView, completionHandler: { () -> Void in
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
})
I have tried using a completionHandler something like func loadPublicFeed(completionHandler: () -> Void) then the code. I have done many variations of this. I feel like I might be on the right path but I just can't nail this down.
You can refactor your function in the following way:
func loadPublicFeed(collectionView: UICollectionView, completionHandler:() -> Void) {
ref = FIRDatabase.database().reference()
ref.child("brackets").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key as String
let post = BracketsPublicFeed(postKey:key, postData: bracketsDict)
self.posts.insert(post, at: 0)
}
}
}
completionHandler();
})
}

Resources