modifying a variable from inside a closure swift/firebase - swift

In the function below, everything works except when I try to get the variable partnerName from point A to point B, i.e. moving the variable in an out of a closure. I am a novice coder so I am hoping that someone can help me discover how to solve this particular issue and point me to a place where I might be able to learn the basics of how to share variables between different parts of a function.
func getAllConversations(handler: #escaping (_ conversationsArray: [Conversation]) -> ()) {
var partner = [""]
var title: String = ""
var partnerName = ""
var conversationsArray = [Conversation]()
REF_CONVERSATION.observeSingleEvent(of: .value) { (conversationSnapshot) in
guard let conversationSnapshot = conversationSnapshot.children.allObjects as? [DataSnapshot] else { return}
for conversation in conversationSnapshot {
let memberArray = conversation.childSnapshot(forPath: "members").value as! [String]
partner = memberArray.filter {$0 != (Auth.auth().currentUser?.uid)!}
if memberArray.contains((Auth.auth().currentUser?.uid)!) {
let newPartner = (String(describing: partner))
title = newPartner.replacingOccurrences(of: "[\\[\\]\\^+<>\"]", with: "", options: .regularExpression, range: nil)
databaseRef.child("bodhi").child(title).observeSingleEvent(of: .value, with: { (snapshot) in
if let bodhiDict = snapshot.value as? [String: AnyObject]
{
//I realize that the variable is being wrongly redeclared here to make this run.
let partnerName = (bodhiDict["Name"] as! String)
print ("partnerName returned from firebase: \(partnerName)")
// Point A: This prints "Sandy"
}
})
print ("partnerName: \(partnerName)")
// Point B: This prints nothing but if I add partnerName = "Sandy", then the function complete
title = partnerName
print ("new title: \(title)")
let conversation = Conversation(conversationTitle: title, key: conversation.key, conversationMembers: memberArray, conversationMemberCount: memberArray.count)
conversationsArray.append(conversation)
}
}
handler(conversationsArray)
}
}
func createGroup(withTitle title: String, andDescription description: String, forUserIds ids: [String], handler: #escaping (_ groupCreated: Bool) -> ()) {
REF_GROUPS.childByAutoId().updateChildValues(["title": title, "description": description, "members": ids])
// need to add code here for slow internet: if successful connection true else error
handler(true)
}
func getAllGroups(handler: #escaping (_ groupsArray: [Group]) -> ()) {
var groupsArray = [Group]()
REF_GROUPS.observeSingleEvent(of: .value) { (groupSnapshot) in
guard let groupSnapshot = groupSnapshot.children.allObjects as? [DataSnapshot] else { return}
for group in groupSnapshot {
let memberArray = group.childSnapshot(forPath: "members").value as! [String]
if memberArray.contains((Auth.auth().currentUser?.uid)!) {
let title = group.childSnapshot(forPath: "title").value as! String
let description = group.childSnapshot(forPath: "description").value as! String
let group = Group(title: title, description: description, key: group.key, members: memberArray, memberCount: memberArray.count)
groupsArray.append(group)
}
}
handler(groupsArray)
}
}
}

I recommend you read the documentation on closures:
Closures
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
Closures: Capturing Values
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID103

Related

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

Trying to get data from Firebase realtime-database

I'm trying to get data from a realtine-database I have in Firebase.
I saw a few answers here but couldn't find a solution that I understood and worked for me.
this is my code:
func ReadTeamPlayers(teamName name: String, completion: #escaping ([Player]?) -> Void) {
var playersArray = [Player]()
ref.child(name).observeSingleEvent(of: .value) { (snapshot) in
if let postDict = snapshot.value as? Dictionary<String, Any>{
for dict in postDict {
playersArray.append(Player(name: dict[snapshot.key] as! String, rank: dict[snapshot.value]))
}
}
}
}
In the player initializer I need to get the player's name and rank.
My Firebase looks like that:
TEAM1
---player1: 10
---player2: 5
---player3: 6
playerX is the player's name for now and the number is the player's rank.
How can I get the info for each player and use it later?
I tried few things but none worked for me.
Edit: I changed the code above to my current one, and this is the error I get now:
Value of tuple type '(key: String, value: Any)' has no member 'subscript'
You need
dic.forEach {
let player = Player(playerName:$0.key, playerRank:$0.value)
arr.append(player)
}
OR
let arr = dic.map { Player(playerName:$0.key, playerRank:$0.value) }
Change response
func ReadTeamPlayers(teamName name: String, completion: #escaping ([Player]?) -> Void) {
func ReadTeamPlayers(teamName name: String, completion: #escaping ([Player]?) -> Void) {
ref.child(name).observeSingleEvent(of: .value) { (snapshot) in
if let mPlayer = snapshot.value as? [String:Int] {
var arr = [Player]()
mPlayer.forEach {
let player = Player(playerName:$0.key, playerRank:$0.value)
arr.append(player)
}
completion(arr)
}else{
completion(nil)
}
}
}
Try this, you are using strange code with generics. Keep it simple
func ReadTeamPlayers(teamName name: String, completion: #escaping ([Player]?) -> Void) {
var playersArray = [Player]()
ref.child(name).observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else {
print("empty snapshot")
return
}
for dict in dictionary {
print("playerName = \(dict.key)")
if let playerRank = dict.value as? Int {
print("playerRank = \(playerRank)")
let player = Player(name: dict.key, rank: playerRank)
playersArray.append(player)
}
}
}
}

How to use flatmap to store snapshot data into a variable

Using the debugger, it shows that snapshot has 3 values but posts has 0 so I think I'm incorrectly using flatMap. Perhaps there is a better way to store the data into posts.
static func observePosts(for user: User = User.current, withCompletion completion: #escaping (DatabaseReference, [Post]) -> Void) -> DatabaseHandle {
let ref = Database.database().reference().child("posts").child(user.username)
return ref.observe(.value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot] else {
return completion(ref, [])
}
let posts = snapshot.flatMap(Post.init)
completion(ref, posts)
})
}
I think snapshot is just a picture of the data available at that time. You cannot directly match the snapshot objects to Post type. Please do as follows
let posts = snapshot.flatMap({(post) -> Post in
let value = post.value as? Dictionary<String, String>
let id = value?["id"] ?? ""
let author = value?["author"] ?? ""
let title = value?["title"] ?? ""
let body = value?["body"] ?? ""
return Post(id: id, author: author, title: title, body: body)
})

Cant fetch SavedPost

My app has an option to save posts for users to watch later. The code is:
func savedPost(for cell: FirstView) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
var post = self.posts[indexPath.item]
guard let currentuserId = Auth.auth().currentUser?.uid else { return }
let targetUid = post.user.uid
guard let postId = post.id else { return }
let ref = Database.database().reference().child("save_post").child(currentuserId).child(postId)
if post.hasSaved {
ref.removeValue { (err, _) in
if let _ = err {
showErr(info: NSLocalizedString("failtoUnsave", comment: ""), subInfo: tryLater)
return
}
post.hasSaved = false
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
} else {
let values = ["userId": targetUid]
ref.updateChildValues(values) { (err, ref) in
if let _ = err {
showErr(info: NSLocalizedString("failtoSave", comment: ""), subInfo: tryLater)
}
post.hasSaved = true
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
}
}
With this code my firebase database in "save_post" has -> currentUseruId -> postid -> postUserId.
On ProfileController users can view saved Posts from "savedPost" Tab. The code is:
var savedPosts = [Post]()
fileprivate func fetchSaved() {
var userIds = [String]()
var postIds = [String]()
guard let uid = self.user?.uid else { return }
let getIDsRef = Database.database().reference().child("save_post").child(uid)
let query = getIDsRef.queryOrderedByKey()
query.observeSingleEvent(of: .value) { (snapshot) in
let dictionary = snapshot.value as? [String: Any]
dictionary?.forEach({ (key,value) in
guard let dic = value as? [String: String] else { return }
postIds.append(key)
userIds.append(dic["userId"] ?? "")
})
var i = 0
while i < userIds.count {
self.fetchPostsWithUserIDPostID(userID: userIds[i], postID: postIds[i])
i += 1
}
}
}
fileprivate func fetchPostsWithUserIDPostID(userID: String, postID: String) {
let getPostRef = Database.database().reference().child("video_list")
getPostRef.child(userID).child(postID).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let ref = Database.database().reference().child("users").child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let user = User(uid: userID, dictionary: dict)
var post = Post(user: user, dictionary: dictionary)
post.id = postID
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("likes").child(postID).child(currentUserUID).observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
post.hasSaved = true
self.savedPosts.append(post)
self.savedPosts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView.reloadData()
})
})
})
}
However, when I click "savedPost" tab, there is no post shown. I don't know where my mistake is. I have all the necessary code under all override func collectionView(....). I believe the error should come from the code listed above. I am sincerely looking for help to resolve this issue. Thanks.
There could be a number of things going on here. It would be good to throw some print statements in there to make sure that 1) the data you're getting back from the database looks like what you expect, and 2) that you're properly parsing it into Post objects. Do you have your cells defined properly for your CollectionView? Also, I don't see where you are defining the data source for the CollectionView.

Accessing data inside a closure after it has been completed

I want to be able to access the results array, after all the data has been added from Firebase to my array. Every time I try this, I get nil array.
Objective is to have a list of location info objects in an array, loaded through Firebase.
My code snippet:
class func loadData(){
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observe(.childAdded,with: { (snapshot) in
print("inside closure")
let values = snapshot.value as? NSDictionary
let name = values?["Name"] as? String ?? ""
let rating = values?["Rating"] as? Int
let latitude = values?["Latitude"] as? Double
let longitude = values?["Longitude"] as? Double
let musicType = values?["Music"] as? String ?? ""
let loc = LocationInfo.init(name: name, rating: rating!, lat:
latitude!, long: longitude!, musicTyp: musicType)
resultsArray.append(loc)
})
}
Try something like this:
class func loadData(completion: #escaping (_ location: LocationInfo) -> Void) {
let root = FIRDatabase.database().reference()
let locationSummary = root.child("LocSummary")
locationSummary.observe(.childAdded,with: { (snapshot) in
print("inside closure")
let values = snapshot.value as? NSDictionary
let name = values?["Name"] as? String ?? ""
let rating = values?["Rating"] as? Int
let latitude = values?["Latitude"] as? Double
let longitude = values?["Longitude"] as? Double
let musicType = values?["Music"] as? String ?? ""
let loc = LocationInfo.init(name: name, rating: rating!, lat:
latitude!, long: longitude!, musicTyp: musicType)
completion(loc)
})
}
In your cycle add something like this:
func getArray(completion: #escaping (_ yourArray: [LocationInfo]) -> Void {
var resultsArray = [LocationInfo]()
let countOfLoadedItems = 0
for item in yourArrayForCycle { // or your own cycle. Implement your logic
loadData(completion: { location in
countOfLoadedItems += 1
resultsArray.append(location)
if countOfLoadedItems == yourArrayForCycle.count {
completion(resultsArray)
}
})
}
}
Then in function, where you wants your data:
getArray(completion: { result in
yourArrayToFill = result
// reload data etc..
})
Something like this. Adapt it to your solution.
Hope it helps