I have made a following feature where users can follow each other. The problem is that it is not being used the way it should. At the moment when a user is writing a post it will be saved in my Firebase database under this reference:
FIRDatabase.database().reference().child("feed-items").childByAutoId()
The feed-items is where all posts are. I am changing that however now, so when a user is posting something it will be saved here:
FIRDatabase.database().reference().child("Users").child(UserID).child("Posts").childByAutoId()
I do that because it somehow tells me that would be easier to only show the posts of the people you follow in my apps feed.
At the moment I am getting all the posts for my feed (from feed-items) like this:
func startObersvingDB() {
FIRDatabase.database().reference().child("feed-items").observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [Sweet]()
for update in snapshot.children {
let updateObject = Sweet(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates.reverse()
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
And then I call startObservingDB() in the viewDidLoad.
If you want to see my Sweet struct here it is:
import Foundation
import FirebaseDatabase
import FirebaseAuth
import UIKit
struct Sweet {
let key: String!
let content: String!
let addedByUser: String!
let profilePhoto: String!
let itemRef: FIRDatabaseReference?
init (content: String, addedByUser: String, profilePhoto: String!, key: String = "") {
self.key = key
self.content = content
self.addedByUser = addedByUser
self.profilePhoto = profilePhoto
self.itemRef = nil
}
init (snapshot: FIRDataSnapshot) {
key = snapshot.key
itemRef = snapshot.ref
path = key
if let theFeedContent = snapshot.value!["content"] as? String {
content = theFeedContent
} else {
content = ""
}
if let feedUser = snapshot.value!["addedByUser"] as? String {
addedByUser = feedUser
} else {
addedByUser = ""
}
if let feedPhoto = snapshot.value!["profilePhoto"] as? String! {
profilePhoto = feedPhoto
} else {
profilePhoto = ""
}
}
func toAnyObject() -> AnyObject {
return ["content":content, "addedByUser":addedByUser, "profilePhoto":profilePhoto!]
}
}
And in my TableViewController I am using this to display name etc. in the custom cell:
var update = updates[indexPath.row]
cell.nameLabel.text = update.addedByUser
etc. etc.
My question is:
How do I change that to only show posts from the people I am following?
Sorry for the long post
Assuming that you are saving your followers list as an dictionary in other parent node like this :-
user_followed_by :{
userID2 : {
userID1 : true,
userID5 : true,
userID6 : true,
userID12 : true,
userID99 : true,
}
}
users :{
userID2 :{
post :{
postAutoID1 : {...},
postAutoID2 : {...},
...
}
}
}
postsToShowToUser :{
userID1 : {
postAutoID1 : true, //These are the post's autoID's of all the users whom
// your current user is following
postAutoID5 : true,
postAutoID3 : true,
},
}
/* // If you choose to declare a separate section of the post Details in your Database.
posts_Of_The_User :{
userID1 : {
postAutoID1 : {...//DETAILS},
postAutoID2 : {...//DETAILS},
postAutoID3 : {...//DETAILS},
....
},
} */
The idea is that whenever a user whom your current user is following makes a post. That post's autoID gets appended in the postsToShowToUser/userID.
That is, if userID2 will make a post then a call will be made to append that post's autoID in all users postsToShowToUser/userID who are following the userID2.
PS:- I strongly suggest that you move your post details out of post section. Make it a separate parent node consisting of multiple postAutoID's as key and there post data as value. It might come in handy later on, also would avoid nesting data, which would help you navigate through your Database.
Related
Super new to coding so apologies if something is super obvious here.
I'm working on an app that I can use to keep track of my weight lifting split. I write the data like this:
public func writeNewExercise(splitName: String, day: Int, exerciseNum: Int, exerciseName: String, sets: String, repsSecs: String, isTimed: Bool, completion: #escaping (Bool) -> Void) {
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/exercise \(exerciseNum)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
}
This gives me a JSON dictionary in Firebase that looks like this:
{
"8aIzPgurRLPPEYDpXWv54r5JjvH3" : {
"splits" : {
"Test Split" : {
"day 1" : {
"exercise 0" : {
"Exercise Name" : "Curls",
"Is Timed" : false,
"Reps or Secs" : "12",
"Sets" : "4"
}
}
}
}
},
What I want to do now is to pull this data so I can insert each exercise into a tableView cell. Don't want to do anything fancy with it -- just be able to view it so I can follow my split. I'm doing this more for practice than practicality. I've tried pulling the data about 15 different ways, and no matter what I do it just won't work. I'm totally stumped. Here is the code I have right now:
public func downloadPost(splitName: String, day: Int, completion: #escaping (Bool) -> Void){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value) { snapshot in
if snapshot.exists(){
for x in 0...100{
let nameValue = snapshot.childSnapshot(forPath: "exercise \(x)/Exercise Name").value
let setsValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets").value
let repsOrSecsValue = snapshot.childSnapshot(forPath: "exercise \(x)//Sets/Reps or Secs").value
let isTimedValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets/Is Timed").value
let exercise = Exercise(name: "\(nameValue!)",
sets: "\(setsValue!)",
repsOrSecs: "\(repsOrSecsValue!)",
isTimed: isTimedValue as? Bool ?? false)
print(exercise.name)
print(exercise.sets)
print(exercise.repsOrSecs)
print(exercise.isTimed)
exerciseArray.append(exercise)
completion(true)
return
}
} else {
print("no snapshot exists")
}
print(exerciseArray)
}
}
Exercise is a custom class I've created that has a name, amount of sets, amount of reps, and a Bool "isTimed". This code prints:
no snapshot exists, []
Trying other things, I've got it to print something like:
null,
0,
0,
false
Some other stuff I've tried has been:
using slash navigation instead of chaining .childs in the .observe.value
using .getData instead of .observe
throwing DispatchQueue.main.async all over the place
making the exerciseRef be the whole database, then calling to the specific point when assigning the snapshot.value
Much else
I've probably put something like 15 hours into just this at this point, and I really cannot figure it out. Any help would be massively appreciated. I'll watch this post closely and post any info that I may have left out if it's needed.
Thanks!
UPDATE
Got everything working by using the code provided by Medo below. For others trying to do something like this, after pulling the array as Medo demonstrated, just set all the labels in your tableViewCell to ExportedArray[indexPath.row].theClassPropertyYouWant
Here is my solution:
public func downloadPost(splitName: String, day: Int, completion: #escaping (([Exercise]) -> ())){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value, with: { snapshot in
guard let exercises = snapshot.children.allObjects as? [DataSnapshot] else {
print("Error: No snapshot")
return
}
for exercise in exercises {
let exerciseData = exercise.value as? [String:Any]
let exerciseName = exerciseData["Exercise Name"] as? String
let isTimed = exerciseData["Is Timed"] as? Bool
let repsOrSecs = exerciseData["Reps or Secs"] as? String
let sets = exerciseData["Sets"] as? String
let exerciseIndex = Exercise(name: "\(exerciseName)",
sets: "\(sets)",
repsOrSecs: "\(repsOrSecs)",
isTimed: isTimed)
exerciseArray.append(exerciseIndex)
}
completion(exerciseArray)
}
}
You can call the function downloadPost and extract the array from it like this:
downloadPost(splitName: "", day: 0, completion: {
aNewArray in
// aNewArray is your extracted array [Exercise]
print("\(aNewArray)")
})
Few things to be aware of:
If you want to ensure that your storing your exercises in order (and extract the data in order) then instead of having exercises 0, 1, 2... (in your database), name it by an id called "childByAutoId". Firebase will auto order them for you as you add/push or extract that data. Replace your writeNewExercise function with:
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
let key = Database.database().reference().childByAutoId().key ?? ""
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/\(key)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
Firebase Realtime Database is a breadth first search and download. So you should probably flatten out your database structure as much as possible. This means observing on exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)") would still download all the exercise days.
I am trying to list all the users from a firebase node in a table view (this is working) but would then like to remove the current user from the list (this is not working).
I've tried to use removeValue() but it still ran with the current user on the table view - also I don't want to remove the user from firebase
I then tried to make it run only if the the user autoId is not equal to the current users Id but it still shows up on the table view cell
Would appreciate any help :)
My firebase structure is like this:
"users" : {
"8x1SGi2P0KVOKm4i60JQLP1bdU63" : {
"fullname" : "bbbb",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2F8x1SGi2P0KVOKm4i60JQLP1bdU63?alt=media&token=7932b14f-c2d8-46fd-9dd1-c607217fe8d3",
},
"B7rwHiCTlphZjKXfPSEzkIwl8RH2" : {
"fullname" : "Aaaa ",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FB7rwHiCTlphZjKXfPSEzkIwl8RH2?alt=media&token=072e1f41-935e-430d-af99-dc640381f8e6",
},
"FRuuk20CHrhNlYIBmgN4TTz3Cxn1" : {
"fullname" : "eeee",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FFRuuk20CHrhNlYIBmgN4TTz3Cxn1?alt=media&token=bf89b903-a51a-4d6d-bdef-fe2667d78842",
},
Code to which lists users:
func observeUsers(completion: #escaping (UserModel) -> Void) {
REF_USERS.observeSingleEvent(of: .childAdded, with: { snapshot in
if let dict = snapshot.value as? [String: Any] {
let user = UserModel.transformUser(dict: dict, key: snapshot.key)
//line below used first to remove value from listing on table view
//Api.User.REF_USERS.child("users").child(Api.User.CURRENT_USER!.uid).removeValue()
//line below - if user autoID is not equal to the current user then list
if user.id != Api.User.CURRENT_USER?.uid {
completion(user)
}
}
})
}
EDIT:
func loadUsers() {
var allUsers = [String]()
Api.User.REF_USERS.observe(.value, with: { snapshot in
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
allUsers.append(key)
print(allUsers)
}
})
}
instead of removing try this
create model of your tabledata
then create an empty array of it
while fetching the data append all except that which has same currentuser.uid
then reload the tableview this will show all the data except the current user
this is the code as promised:
but this need a little modification in your database
make your database like this
"users":{
"childbyautid":{
"fullname": "name",
"profileimageurl": "your url",
"userid": "userid"
}
then you can write like this
var myArr = [String]()
Database.database().reference.child("users").observe(.value){(snapshot) in
if snapshot.childcount > 1 { self.myArr.removeAll()
for data in snapshot.children.allObjects as! [DataSnapshot]{
if let d = data.value as? [string: any]{
if Auth.auth.currentuser.uid != d["userid"]{
myArr.append(d["name"]}else{print("this is the user itself"}}}
You can control this in you loadUsers by adding userId check like below
func loadUsers() {
var allUsers = [String]()
Api.User.REF_USERS.observe(.value, with: { snapshot in
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
if key != currentUser.id{
allUsers.append(key)
}
print(allUsers)
}
})
}
I am attempting to simply read into the database that is structured as stated below. I am attempting to read the user's "userType" and use it in the following if statements below. Any help is appreciated!
Swift Code:
// Create firebase reference and link to database
var dataRef : DatabaseReference?
dataRef = Database.database().reference()
let userID = Auth.auth().currentUser!.uid // Get the User's ID
// Gather user's type (Customer or Company)
/*Use this space to gather the user's type into some variable named currUserType*/
if (currUserType == "Customer"){
self.performSegue(withIdentifier: "LoginToCustomer", sender: self)
print("User: " + userID + " has been signed in!")
}
else if (currUserType == "Company"){
self.performSegue(withIdentifier: "LoginToHost", sender: self)
}
else{
self.showMessage(alertTitle: "Error",
alertMessage: "Please report the following error with a description of what lead to to the error.",
actionTitle: "Dismiss")
}
Database Structure:
"Users" : {
"ZFH0lFe1fIb5bwSO2Q95ektD33L2" : {
"email" : "cust#test.com",
"userType" : "Customer"
}
First take the ref like i have took below:
let dbRef = Database.database().reference().child("Users")
Then create model like i have created below:
class Users {
var email: String?
var userType: String?
init(email: String, userType: String) {
self.email = email
self.userType = userType
}
}
Then create completion Handler like i have created below:
func getUsersData(handler: #escaping (_ usersArray: [Users]) -> ()) {
var usersArray = [Users]()
dbRef.observe(.value) { (datasnapshot) in
guard let usersnapshot = datasnapshot.children.allObjects as? [DataSnapshot] else { return }
for user in usersnapshot {
let email = user.childSnapshot(forPath: "email").value as! String
let userType = user.childSnapshot(forPath: "userType").value as! String
let userObj = Users(email: email, userType: userType)
usersArray.append(userObj)
}
handler(usersArray)
}
}
simply call this function which returns the whole array of users.
Refrence https://firebase.google.com/docs/database/ios/read-and-write#reading_and_writing_data
I'm creating an app that allows notes to be shared. I'm having trouble with the Firebase database and feel that the amount of lines of code I am using to get information the user needs is more than I actually need and on top of that doesn’t work. Below is a screenshot of my database and a show of the code I’ve used. There are 3 different account types in my app. Parent, Child, and Other.
The Child can only see their own notes
The Parent can see everyone who’s written notes in for their code
The Other is supposed to see their notes and the parents notes per
code.
I am attempting to show the users whose notes can be seen in a collection view. So far it works for Parents and Children but not for the 'Other' which I am trying to fix.
This is my database. Right now, a Parent makes the account which generates a unique code. Then an Other or Child can join by typing the code in on a ViewController. For a child account it will automatically add the code to the account of the child in the user section and for the Other it is supposed to make a new section (I added a picture of what it looked like before I changed it). All users that have a certain code are added to the code section under that code.
{
"codes" : {
"ParVWb" : {
"totalUsers" : 2,
"users" : {
"VWbSR8qQyWd9deka6tgbxFC9ahs1" : {
"numberofNotes" : 0
},
"qm12qgkWfdbtSMMx58wIrxKuLKh1" : {
"numberofNotes" : 0
}
}
}
},
"users" : {
"JN47TBdI4EYX0NvoSHN21o1xFH92" : {
"email" : "other1#restart.mocs",
"fullName" : "Other 1 Name",
"type" : "Other",
"uid" : "JN47TBdI4EYX0NvoSHN21o1xFH92"
},
"VWbSR8qQyWd9deka6tgbxFC9ahs1" : {
"code" : "ParVWb",
"email" : "parentemail#restart.mocs",
"fullName" : "Parent Name",
"type" : "Parent",
"uid" : "VWbSR8qQyWd9deka6tgbxFC9ahs1"
},
"qm12qgkWfdbtSMMx58wIrxKuLKh1" : {
"code" : "ParVWb",
"email" : "childname#restart.mocs",
"fullName" : "Child Name",
"type" : "Child",
"uid" : "qm12qgkWfdbtSMMx58wIrxKuLKh1"
}
}
}
I used to have the use section look like this but now because of the large amount of reference blocks, it doesn't run the part that adds the "codes" section to the 'Other' user.
This is the function I have in my CollectionViewController that obtains all the needed users depending on the currentUser type.
func fetchConnections() {
ref.child("users").child(userID!).observe(.value, with: { (snapshot) in
let user = User(snapshot: snapshot)
if user.type == "Parent" {
let code = user.code as? String
let codeRef = databaseRef.child("codes").child(code!).child("users")
codeRef.observe(.value, with: { (codeSnapshot) in
if let users = codeSnapshot.value as? [String:AnyObject] {
DispatchQueue.main.async(execute: {
for user in users {
if let dict = user.value as? [String: Any] {
let userInformation = cellInfo(uid: user.0, numberofNotes: (dict["numberofNotes"] as? Int)!, code: self.code)
self.userCollection.append(userInformation)
self.collectionView.reloadData()
}
}
})
}
}) { (error) in
print(error.localizedDescription)
}
} else if user.type == "Child" {
let code = user.code
self.ref.child("codes").child(code!).child("users").child(self.userID!).observe(.value, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
print(user.key, (dict["numberofNotes"] as? Int)!)
let userInformation = cellInfo(uid: (user.key as? String)!, numberofNotes: (dict["numberofNotes"] as? Int)!, code: "code")
self.userCollection.append(userInformation)
self.collectionView.reloadData()
}
})
} else if user.type == "Other" {
self.ref.child("users").child(self.userID!).child("codes").observe(.value, with: { (snap) in
if let codes = snap.value as? [String:AnyObject] {
for code in codes {
if code.key == "numberofCodes" {
print("ignore")
} else { // If it's an actual code
self.ref.child("users").child(code.0).observeSingleEvent(of: .value, with: { (codeSnap) in
let user = User(snapshot: codeSnap)
let codetoFetch = user.code
self.ref.child("codes").child(codetoFetch!).child("users").child(self.userID!).observe(.value, with: {(codeSnapshot) in
if let users = codeSnapshot.value as? [String:AnyObject] {
DispatchQueue.main.async(execute: {
for user in users {
if let dict = user.value as? [String: Any] {
let userInformation = cellInfo(uid: code.0, numberofNotes: (dict["numberofNotes"] as? Int)!, code: self.code)
//self.userCollection.append(userInformation)
//self.collectionView.reloadData()
}
}
})
}
})
})
}
}
}
})
}
})
}
How can I obtain all of the codes that the 'Other' is a part of and all of the UID's & NumberofNotes for the 'Parents' of that code so that I can put them into the collectionView? (This code works for Parent and Child but if the way I'm doing it is long and ineffective please let me know.)
This problem may be difficult to explain.
Here is the JSON that is generated by firebase:
{
"users" : {
"2W1Sse5kiZarFQ5k8UKjP87D8Rk2" : {
"PlainNote" : {
"-KIZYVAGZQczTSI1L4Qi" : {
"note" : "Finally works",
"title" : "Whats up"
},
"-KIZY_M3FMW0m8mgx4Am" : {
"note" : "Aye",
"title" : "Db"
},
"-KIZpAEsa7-wSCOJfHvv" : {
"note" : "This wont work for some reason",
"title" : "Helloo"
}
},
"email" : "jacobsiegel#gmail.com"
},
"XTjy66z8xEWtEiLx6E0mOo1JEaz2" : {
"email" : "123#gmail.com"
},
"rfzV1saUf3RtPu7YcqJ0VyAOgmd2" : {
"PlainNote" : {
"-KIZpfbfHcePU3E8HpeU" : {
"note" : "Now i only see my own data",
"title" : "Hello"
}
},
"email" : "whatsup#gmail.com"
}
}
}
Here is how I generate each plain note in swift:
let title = plainNoteTitleTextField.text!
let note = bodyOfNoteTextView.text!
let usersId = FIRAuth.auth()?.currentUser?.uid
if title != "" || note != "" {
let data = [
"title": title,
"note": note
]
self.usersRef.child(usersId!).child("PlainNote").childByAutoId().setValue(data)
After this, I populate the tableview in realtime with the firebase realtime database:
override func viewDidAppear(animated: Bool) {
var localNotes = [String]()
var localBody = [String]()
let usersId = FIRAuth.auth()?.currentUser?.uid
usersRef.child(usersId!).child("PlainNote").observeEventType(.ChildAdded, withBlock: { snapshot in
let title = snapshot.value?.objectForKey("title") as! String
var x = snapshot.value?.objectForKey("note") as! String
if x.characters.count > 20 {
x = x[0...20] + "..."
} else {
x = x + "..."
}
localNotes.insert(title, atIndex: 0)
localBody.insert(x, atIndex: 0)
self.notes = localNotes
self.noteBody = localBody
self.tableView.reloadData()
})
}
My problem: I am trying to access the PlainNote references so that when a tableview cell is tapped, I can pull the specific note title and body from firebase. I am not sure how I would do that without looking at my database every time, because firebase creates a unique reference each time a new note is created.
(P.S.: If you think you understand, but need more details, don't hesitate to ask!)
Thank you.
not sure if you have the answer yet, but here it is just in case (I had the same problem). The way that I understand it is that you want to obtain the unique keys that are generated each time so that you can access the data stored underneath a single one. You could generate a string array and then use this to extract the value of a key at a single index which can then be fed back into your data retrieval code. This is done like so:
usersRef.child(usersId!).child("PlainNote").observeEventType(.ChildAdded, withBlock: { snapshot in
var idKeys = [String]() //this will hold your keys
for snap in snapshot.children.allObjects {
let id = snap as! FIRDataSnapshot
idKeys.append(String(id.key))
}}
Now you have your keys, you can select one using var selectedKey = idKeys[x].
Hope this helps