How to get follower and following count quickly with Firebase and Swift - swift

I am currently trying to fetch all the followers for a specific user with firebase. In my didSet clause, I call the function setFollowingCount() to fetch the users that the current user follows and assign it to a text field:
var user: User? {
didSet {
setFollowingCount()
guard let following = self.user?.following else {return}
let attributedText = NSMutableAttributedString(string: "\(following)\n", attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: "followers", attributes: [NSAttributedStringKey.foregroundColor: UIColor.lightGray, NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)]))
self.followingLabel.attributedText = attributedText
}
}
The setFollowingCount() function is:
func setFollowingCount(){
var i = 0
guard let userId = self.user?.uid else { return }
Database.database().reference().child("following").child(userId).observe(.value) { (snapshot) in
self.user?.following = Int(snapshot.childrenCount)
}
}
The problem is that this takes very long to load and often freezes the entire app when you look at a user's profile. How can I speed this up or make it work more efficiently?

self.user?.following = Int(snapshot.childrenCount)
Is not an efficient solution. .childrenCount actually loops over the snapshot and counts all of the children which is going to be slow.
Instead you want to store the number of followers as a single value you can retrieve it faster.
following: {
uid: {
followingCount: 100,
follwersCount: 150
}
}
Then you can query like this:
Database.database().reference().child("following").child(userId).observeSingleEvent(of: .value) { (snapshot) in
if let counts = snap.value as? [String: AnyObject] }
let followingCount = counts["followingCount"] as? Int
let followersCount = counts["followersCount"] as? Int
// save these values somewhere
}
})
I would also recommend you increment / decrement the follower counts in a transaction block so the count doesn't get messed up. That can look something like this:
static func incrementCount(countName: String) {
if let uid = Auth.auth().currentUser?.uid {
let databaseReference = Database.database().reference()
databaseReference.child("following").child(uid).runTransactionBlock { (currentData: MutableData) -> TransactionResult in
if var data = currentData.value as? [String: Any] {
var count = data[countName] as! Int
count += 1
data[countName] = count
currentData.value = data
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}
}
}
Lastly,
If you're going to use .observe you need to remove the reference. In this case though you aren't looking for updates so you can use .observeSingleEvent

Related

How to store a data from a closure firebase in a local variable?

I'm trying to store the value of the completion block into a dictionary. But I don't know how to access and store it in a local dictionary variable.
var id = String()
var answeredDict = [String:[String]]()
var answeredDictUsers = [String:String]()
override func viewDidLoad() {
super.viewDidLoad()
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
}
}
print(answeredDictUsers)
}
the print(answeredDictUsers) gives [:] (empty dictionary)
Data is loaded from Firebase asynchronously. By the time your print(answeredDictUsers) runs, the code inside the callback hasn't run yet. And since self.answeredDictUsers.updateValue(name, forKey: user) hasn't run yet, the print sees an empty array. You can easily test this for yourself by placing breakpoints on both those lines and running the code in the debugger.
This means that any code that needs data from the database, must be (called from) inside the callback/completion handler that gets that data from the database.
A very simple example:
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
print(answeredDictUsers)
}
}
The above will print the dictionary each time it has loaded one of your users' data.
If you only want to print the dictionary once the data for all users has been loaded, you could for example keep a counter:
let count = 0
for user in answeredDict.keys{
let ref = Database.database().reference(fromURL: "URL").child("users/\(user)")
ref.child("name").observeSingleEvent(of: .value) { (snap) in
guard let name = snap.value as? String else { return }
self.answeredDictUsers.updateValue(name, forKey: user)
count = count + 1
if count == dict.count {
print(answeredDictUsers)
}
}
}

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

How can I pick three random elements out of a dictionary in Swift 4.1

I am having a problem picking three random elements out of a dictionary.
My dictionary code:
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
var dict = childSnap.value as! [String: Any]
}
})
You can use an array if keys are integers.
if you want to use a dictionary only then below mentioned code might be helpful for you
var namesOfPeople = [Int: String]()
namesOfPeople[1] = "jacob"
namesOfPeople[2] = "peter"
namesOfPeople[3] = "sam"
func makeList(n: Int) -> [Int] {
print(namesOfPeopleCount)
return (0..<n).map { _ in namesOfPeople.keys.randomElement()! }
}
let randomKeys = makeList(3)
You can try this for older version Of Swift where randomElement() is not available
let namesOfPeopleCount = namesOfPeople.count
func makeList(n: Int) -> [Int] {
return (0..<n).map{ _ in Int(arc4random_uniform(namesOfPeopleCount)
}
#Satish answer is fine but here's one which is a bit more complete and selects a random user from a list of users loaded from Firebase ensuring a user is only selected once.
We have have an app with two buttons
populateArray
selectRandomUser
and we have a UserClass to store our user data for each user.
class UserClass {
var uid = ""
var name = ""
init(withSnapshot: DataSnapshot) {
let dict = withSnapshot.value as! [String: Any]
self.uid = withSnapshot.key
self.name = dict["Name"] as! String
}
}
and an array to store the users in
var userArray = [UserClass]()
When the populateArray button is clicked this code runs
func populateArray() {
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let user = UserClass(withSnapshot: snap)
self.userArray.append(user)
}
print("array populated")
})
}
and then to select a random user use this code.
func selectRandomUser() {
if let someUser = userArray.randomElement() {
print("your random user: \(someUser.name)")
let uid = someUser.uid
if let index = userArray.index(where: { $0.uid == uid } ) {
userArray.remove(at: index)
}
} else {
print("no users remain")
}
}
This code ensures the same user is not selected twice. Note that this is destructive to the array containing the users so if that's unwanted, make a copy of the array after it's populated and work with that.

Firebase one of two observers not working

I have two observers, the second observer is dependent on the first observers value. I can't seem to get the first observer to work, I am not getting any errors on Xcode. The first function has to check the Users profile for information and then use that information to search for different information in the database. Here is my code:
func loadposts() {
ref = Database.database().reference()
let trace = Performance.startTrace(name: "test trace")
trace?.incrementCounter(named:"retry")
let userID = Auth.auth().currentUser?.uid
print(userID!)
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let one1 = value?["Coupon Book"] as? String ?? ""
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
}) { (error) in
print(error.localizedDescription)
}
delay(0.1) {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
Database.database().reference().child("Coupons").child(self.bogus.string(forKey: "bogus")!).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] as! String
let offerx = dict["offer count"] as! String
let logocomp = dict["logo"] as! String
let actchild = dict["childx"] as! String
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
self.tableview.reloadData()
print(self.posts)
}
}
}
trace?.stop()
}
Any help is appreciated.
self.bogus.string(forKey: "bogus"))" is nil because observeSingleEvent is an async method, so to get the required results you need to call the second observer inside the first observer or you can use the completion handler
You can use the completionHandler like this:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
func firstObserverMethod(completionCallback: #escaping () -> Void) {
ref.child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let value = snapshot.value as? [String: Any] {
let one1 = value["Coupon Book"] as? String
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
completionCallback()
}
}) { (error) in
print(error.localizedDescription)
}
}
Now using the above method:
firstObserverMethod {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
guard let bogusString = self.bogus.string(forKey: "bogus") else {
print("bogus is not set properly")
return
}
Database.database().reference().child("Coupons").child(bogusString).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] ?? ""
let offerx = dict["offer count"] ?? ""
let logocomp = dict["logo"] ?? ""
let actchild = dict["childx"] ?? ""
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
DispatchQueue.main.async {
self.tableview.reloadData()
}
print(self.posts)
}
}
}
Note: You should use optional binding to get the values from optional
Since you are using the result of the 1st observer in the reference of your 2nd observer, it's a very bad idea to add the 2nd observer right below the first observer. And adding a delay won't be a viable solution : these two calls are asynchronous, which means that the reason why you are not getting might very likely be because the 2nd observer is triggered even before the 1st has returned any data.
The solution here, would be using a completion handler, or you could just incorporate your 2nd observer inside the completion block of the 1st, to be make sure that the proper order (1st observer -> 2nd observer) will always be respected.
It would look somehow like this:
func loadposts() {
// ...
// 1st Observer here
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get your value here
guard let one1 = snapshot.childSnapshot(forPath: "Coupon Book").value as? String else { return }
// 2nd Observer here. Now you can use one1 safely:
Database.database().reference().child("Coupons").child(one1).observe(.childAdded) { (Snapshot : DataSnapshot) in
// ...
}
})
}
Now, a couple of things that you could also improve in your code, while not directly related to the question:
I would suggest you to make use of guard statements instead force-unwrapping, which may end up in crashing your app at some point.
For example, you could check whether your current user exist or not like so:
guard let currentUserID = Auth.auth().currentUser?.uid else {
return
}
// Now you can use safely currentUserID
Also, when you try to get the data out of the snapshot, it's not a good idea either, to use force-casting. You would better write it in this way:
yourRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let text = child.childSnapshot(forPath: "text").value as? String, let somethingElse = child.childSnapshot(forPath: "otherValue").value as? NSNumber else {
return
}
// And so on, depending of course on what you have in your database.
}

Firebase Swift query and client side fan out

I have been on this issue for over three days, i have research and came across other similar questions on SO which relates to my issue but those fix could not solve mine hence the reason am asking this question.
I have a users, posts and users-posts node in firebase as shown below. I want to run a query on the node such that if two users are friends they can see each others post. But if they are not friends they cannot see each others posts
Users
123840ajldkjfas0d9
username: Joe
friend
78983049802930laks: true
78983049802930laks: true
4563049802930laks
username: Ken
friend
123840ajldkjfas0d9: true
78983049802930laks
username: Pean
friend
123840ajldkjfas0d9: true
posts
876f92fh02hfj02930239
post: This is cool
whoposted: 123840ajldkjfas0d9
39fh938hqw9320923308
post: I love pizza
whoposted: 78983049802930laks
users-posts
123840ajldkjfas0d9
876f92fh02hfj02930239: true
78983049802930laks
39fh938hqw9320923308: true
This is my query currently, it is showing all post for all users whether they are friends or not. Please i need help with this.
DataService.ds.REF_USERS.observe(.value, with: { (userSnapshot) in
if let snapshot = userSnapshot.children.allObjects as?
[FIRDataSnapshot]{
for userSnap in snapshot{
print("snapshot.key: \(userSnap.key)")
let userKey = userSnap.key
if var userDict = userSnap.value as? Dictionary<String,
AnyObject>{
let postUserPicUrl = userDict["profileImgUrl"] as? String
if let firstName = userDict["firstName"] as? String{
("firstName: \(firstName)")
DataService.ds.REF_POST.observeSingleEvent(of: .value, with: {
(postSnapshot) in
if let postSnapshot = postSnapshot.children.allObjects as?
[FIRDataSnapshot]{
for postSnap in postSnapshot{
if var postDict = postSnap.value as? Dictionary<String, AnyObject>{
if let refPostUserKey = postDict["user"] as? String{
if userKey == refPostUserKey{
DataService.ds.REF_BLOCK_USER.observeSingleEvent(of: .value, with: {
(blockUserSnapshot) in
if let blockUserSnapshot = blockUserSnapshot.children.allObjects as?
[FIRDataSnapshot] {
for blockUserSnap in blockUserSnapshot{
if var blockUserDict = blockUserSnap.value as? Dictionary<String,
AnyObject> {
if let user = blockUserDict["user"] as? String{
if firstName != user {
postDict["postUserPicUrl"] = postUserPicUrl as AnyObject?;
let postKey = postSnap.key
let post = Post(postKey: postKey, postData: postDict)
self.posts.append(post)
}
}
}
}
}
self.tableView.reloadData()
})
}
}
}
}
}
self.tableView.reloadData()
})
}
}
}
}
self.tableView.reloadData()
})
}
I mean this with no disrespect, but you are not utilizing these queries well with each nested within another. Also, make sure you update all of your queries. The Post query uses the old formatting while your user query is up to date.
You should create 3 dictionaries to hold the data for each node Users, posts, users-posts as well as a var to hold the current user string and a dictionary to contain the post data:
var users = [String:Any]()
var posts = [String:Any]()
var usersposts = [String:Any]()
var currentUserKey:String!
var visibleposts = [String:Any]()
Then have three separate queries to get the data. Currently it does not appear that you are querying for any specific users so I will do the same:
func getUserData(){
DataService.ds.REF_USERS.observe(.childAdded, with: {snapshot in
let key = snapshot.key
let data = snapshot.value as? [String:Any] ?? [:]
self.users[key] = data
})
}
func getPostsData(){
DataService.ds.REF_POST.observe(.childAdded, with: {snapshot in
let key = snapshot.key
let data = snapshot.value as? [String:Any] ?? [:]
self.posts[key] = data
self.refreshPosts()
})
}
func getUsersPostsData(){
DataService.ds.REF_BLOCK_USERS.observe(.childAdded, with:{snapshot in // I am guessing you have the users posts here?? there doesn't seem to be sample data for blocked users in your OP
let key = snapshot.key
let data = snapshot.value as? [String:Any] ?? [:]
self.usersposts[key] = data
self.refreshPosts()
})
}
Now get the current user before firing off these queries in the view did load and then call each query.
override func viewDidLoad(){
self.currentUserKey = (FIRAuth.auth()?.currentUser?.uid)!
/* you may want to do some error handling here to ensure the user
is actually signed in, for now this will get the key if
they are signed in */
self.getUserData()
self.getPostsData()
self.getUsersPostsData()
// data will be refreshed anytime a child is added
}
func refreshPosts(){
self.validposts = [:]
let validUsers = [String]() // this will hold the valid keys to get posts
validUsers.append(self.currentUserKey)
let currentUserData = users[self.currentUserKey] // filter the current user data to get the friends
// get friends keys
let friendsData = currentUserData["friends"] as? [String:Any] ?? [:]
for key in friendsData.keys {
// add friends posts to the validposts data
validUsers.append(key)
}
// get current users posts:
for (key,value) in self.posts {
let postData = value as? [String:Any] ?? [:]
let whoposted = postData["whoposted"] as? String ?? ""
if validUsers.contains(whoposted){
self.validposts[key] = postData
}
}
// access the self.validposts data in your UI however you have it setup
// The child added queries above will continue to fire off and refresh
// your data when new posts are added.
// I am still not clear what the usersposts data is for so it is omitted here.
}