How to retrieve a variable from a Firebase Database in Swift - ios

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

Related

Firebase updateChildValue works in one case but doesn't work for the next case

I am trying to recreate the instagram follow and unfollow functionality. When the follow button is pressed, I call the follow function in the user.swift file. The user-followers structure works perfectly but the user-following database call does not seem to work. Both have the same logic, just opposite declarations. This might sound vague but the code is self-explanatory.
user-followers structure works perfectly but user-following structure does not seem to get created at firebase.
class User{
var username: String!
var name: String!
var profileImageUrl: String!
var uid: String!
var isFollowed = false
init(uid: String, dictionary: Dictionary<String, AnyObject>){
self.uid = uid
if let username = dictionary["username"] as? String {
self.username = username
}
if let name = dictionary["name"] as? String {
self.name = name
}
if let profileImageUrl = dictionary["profileImageUrl"] as?
String {
self.profileImageUrl = profileImageUrl
}
}
func follow(){
guard let currentUid = Auth.auth().currentUser?.uid else {
return }
// set is followed to true
self.isFollowed = true
// add followed user to current user-following structure
Database.database().reference().child("user-
following").child(currentUid).updateChildValues([self.uid: 1])
// add current user to followed user-follower structure
Database.database().reference().child("user-
followers").child(self.uid).updateChildValues([currentUid: 1])
}
func unfollow(){
guard let currentUid = Auth.auth().currentUser?.uid else {
return }
// set is followed to false
self.isFollowed = false
// remove user from current user-following structure
Database.database().reference().child("user-
following").child(currentUid).child(self.uid).removeValue()
// remove current user from user-follower structure
Database.database().reference().child("user-
followers").child(self.uid).child(currentUid).removeValue()
}
)

Values not being set to User object correctly

I have a User object..
class User: NSObject {
var uid: String!
var handle: String!
var name: String!
var profilePicture: String!
var gender: String!
var rooms: [String : AnyObject]!
}
.. which has its values set when the user signs up for the app, using the below function to set the values in both Firebase and the User object:
// Set inital user info to User object & Firebase
func setUserInfo() {
let userInit = User()
let userRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users").childByAutoId()
let params = ["fields" : "first_name"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(let error):
print("error in graph request:", error)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
print(responseDictionary)
// Name
if let name = responseDictionary["first_name"] {
userRef.child("name").setValue(name)
// Profile pic
let facebookID: NSString = (responseDictionary["id"] as! NSString)
let profilePic = "https://graph.facebook.com/\(facebookID)/picture?type=large&return_ssl_resources=1"
userRef.child("profilePicture").setValue(profilePic)
// Handle
// Also check DB to make sure handle isn't already in use
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
userRef.child("handle").setValue(handle)
// Gender
var gender = ""
switch self.genderString {
case "male":
gender = "male"
userRef.child("gender").setValue("male")
case "female":
gender = "female"
userRef.child("gender").setValue("female")
default:
gender = "other"
userRef.child("gender").setValue("other")
}
// UID
if let uid = FIRAuth.auth()?.currentUser?.uid {
userInit.uid = uid
userRef.child("uid").setValue(uid)
print("uid: \(uid)")
}
// Set all values to User object
userInit.name = name as! String
userInit.handle = handle
userInit.gender = gender
userInit.profilePicture = profilePic
self.user.append(userInit)
print("name: \(name)")
print("handle: \(handle)")
print("gender: \(gender)")
print("profilePic: \(profilePic)")
}
}
}
}
}
}
}
With this, all the user info is uploaded to Firebase just fine. However when I try to print the values of the User object in the next view controller:
let user = [User]()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let users = User()
print("name: \(users.name)")
print("handle: \(users.handle)")
print("gender: \(users.gender)")
print("profilePic: \(users.profilePicture)")
}
All are printed as nil. Are the values not getting set to the object, or am I trying to access them wrong?
The point is that you are creating new empty class object in viewDidAppear(_ animated: Bool)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let users = User() //you created new empty object of User Type
print("name: \(users.name)")
print("handle: \(users.handle)")
print("gender: \(users.gender)")
print("profilePic: \(users.profilePicture)")
}
and printing all values will give you nil as it is empty. So way which will work is to pass userInit object to second view controller having constant var myUser
var myUser:User!
in prepareForSegue from fristViewController to second you can supply user Object so hereby the code for secondVC class will become
SecondViewController.swift
var myUser:User!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let users = User() //you created new empty object of User Type
users = myUser
print("name: \(users.name)")
print("handle: \(users.handle)")
print("gender: \(users.gender)")
print("profilePic: \(users.profilePicture)")
}

Displaying firebase child from user.uid to username in Swift 3

I have a view controller for My Profile. Logging in allows the profile page to appear without errors but when signing up, app crashes when pressing the contacts button located at bottom of view controller as seen below.
The process:
User Signs Up:
func signUp(_ email: String, usersname: String, password: String, data: Data!, loginHandler: LoginHandler?) {
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
// Show error to user
self.handleFirebaseErrors(err: error as! NSError, loginHandler: loginHandler)
} else { // success creating user
if user?.uid != nil { // if there is a valid user id
// Store user to database
self.setUserInfo(user, usersname: usersname, email: email, password: password, data: data!)
// Log In the user
self.login(email: email, password: password, loginHandler: loginHandler)
}
}
})
}
As in the signUp(), setUserInfo() is called, which contains images, and then calls saveUser()
Save User
func saveUser(_ user: FIRUser!, usersname: String, email: String, password: String) {
// Create the user dictionary info
let userInfo = ["email": user.email!, "password": password, "usersname": usersname, "uid": user.uid, "photoUrl": String(describing: user.photoURL!)]
// create user reference
let userRef = DataService.Instance.dbRef.child("riders").child(user.uid)
// Save the user info in the database
userRef.setValue(userInfo)
}
Logs In
func login(email: String, password: String, loginHandler: LoginHandler?) {
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
self.handleFirebaseErrors(err: error as! NSError, loginHandler: loginHandler)
} else {
loginHandler?(nil, nil)
}
})
}
The problem here is in saveUser():
At the moment, firebase shows the user.uid but I want it to show the username of the user.
let userRef = DataService.Instance.dbRef.child("riders").child(usersname)
With the above code, once the contacts button is pressed on the RidersVC, it crashes the app with error:
fatal error: unexpectedly found nil while unwrapping an Optional value
on line 56 of MyProfileVC:
let imageUrl = String(user.photoUrl)
Any ideas as how I can get the username to be displayed as the child of "riders" instead of the user.uid without it crashing?
MyProfileVC.swift
if FIRAuth.auth()?.currentUser == nil {
let vc = UIStoryboard(name: "Rider", bundle: nil).instantiateViewController(withIdentifier: "Login")
present(vc, animated: true, completion: nil)
} else {
dbRef.child("riders/\(FIRAuth.auth()!.currentUser!.uid)").observe(.value, with: { (snapshot) in
DispatchQueue.main.async(execute: {
let user = User(snapshot: snapshot)
self.username.text = user.usersname
self.email.text = FIRAuth.auth()?.currentUser?.email
let imageUrl = String(user.photoUrl)
Firebase Database Structure: (how I want it to be)
{
"riders" : {
"rider 1" : {
"email" : "rider1#me.com",
"password" : "whatever",
"photoUrl" : "https://firebasestorage.googleapis.com/...",
"usersname" : "rider 1"
}
}
}
User.swift
struct User {
let usersname: String!
let email: String!
let password: String!
let photoUrl: String!
var ref: FIRDatabaseReference?
var key: String
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
ref = snapshot.ref
let snapshotValueUsersname = snapshot.value as? NSDictionary
usersname = snapshotValueUsersname?["usersname"] as? String
let snapshotValueEmail = snapshot.value as? NSDictionary
email = snapshotValueEmail?["email"] as? String
let snapshotValuePass = snapshot.value as? NSDictionary
password = snapshotValuePass?["password"] as? String
let snapshotValuePhoto = snapshot.value as? NSDictionary
photoUrl = snapshotValuePhoto?["photoUrl"] as? String
}
Firebase structure - (the way it is now)
{
"drivers" : {
"RideRequests" : {
"europeanjunkie" : {
"active" : true,
"latitude" : "45.267",
"longitude" : "-66.059",
"userId" : "5c17ByRJljZFcM703Vqn5eSFwYJ3",
"username" : "europeanjunkie"
}
}
},
"riders" : {
"5c17ByRJljZFcM703Vqn5eSFwYJ3" : {
"email" : "europeanjunkie#me.com",
"password" : "whatever",
"photoUrl" : "https://firebasestorage.googleapis.com",
"uid" : "5c17ByRJljZFcM703Vqn5eSFwYJ3",
"usersname" : "europeanjunkie"
}
}
}
Here's some stuff to consider - a little, some or all may get you headed in the right direction. Also, you can probably remove all of the DispatchQueue calls as Firebase does most of the heavy lifting for you, and with proper code structure, they are not needed.
1) A Swifty user class
class UserClass {
var usersname = ""
var email = ""
var password = ""
var photoUrl = ""
var uid = ""
init(withSnapshot: FIRDataSnapshot) {
let dict = withSnapshot.value as! [String:AnyObject]
uid = withSnapshot.key
usersname = dict["usersname"] as! String
email = dict["email"] as! String
password = dict["password"] as! String
photoUrl = dict["photoUrl"] as! String
}
}
note that we are using the var uid of each user to identify them (their 'key')
The structure that matches that class
users
uid_0
email: "bill#email.com"
password: "myPassword"
photoUrl: "http://www.url.com"
usersname: "Bill"
uid_1
email: "leroy#email.com"
password: "aPassword"
photoUrl: "http://www.anotherUrl.com"
usersname: "Leroy"
Notice again the users and their associated info are stored within the /users node in each child node that has that users uid as the key.
And some code that reads in uid_0, prints the uid and name. This code is a one-shot so it reads uid_0, but does NOT leave an observer attached to the node.
let userRef = rootRef.child("users/uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let aUser = UserClass(withSnapshot: snapshot)
print("uid \(aUser.uid) has name \(aUser.usersname)")
})
Now the Geofire node would like something like this
user_locations
uid_0
//geofire data
uid_1
//geofire data
So now there is a direct correlation between the users node and their location.
In general, it's a good idea to disassociate node names (keys, which are static data) from the data they contain, which is dynamic.
With the structure in the initial question, imagine if 'europeanjunkie' changed his name to 'europeanjunkieDude'. Every place you reference 'europeanjunkie' would then have to be changed - and if it's used as a key, the entire node would have to be read in, deleted, updated, and re-written.
Using child keys created by Firebase, uid's and childByAutoId(), removes that issue.
Hope that helps!
In my opinion, if you want to query the username as the keyword. There are two possible ways to struct your dictionary.
First, use childByAutoId, username and userid will be at the same level, so you can get which value you like.
{
"riders" : {
"-KQaU9lVcUYzIo52LgmN" : {
"email" : "rider1#me.com",
"password" : "whatever",
"photoUrl" : "https://firebasestorage.googleapis.com/...",
"usersname" : "rider 1",
"userid" : "rider 1"
}
}
}
Second, make username as the child of riders. However, there would be tons of Mike.
{
"riders" : {
"username" : {
"email" : "rider1#me.com",
"password" : "whatever",
"photoUrl" : "https://firebasestorage.googleapis.com/...",
"userid" : "rider 1"
}
}
}

Show posts from the users you are following - swift

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.

Capture username to write in new post

When a user signs in, their UID is set to standardUserDefaults(). Also, the users' profile data is saved under a child named their uid.
When the user creates a post, I would like to attach their username/display name to the post.
I've set up a function to fetch the current user's username, but whenever I submit a post, it seems as though the closure is not being executed.
The post model:
class PostModel {
var postBody = String()
var creationDate = String()
var postUID = String()
var userName = String()
init(postBody: String) {
self.postBody = postBody
let dateObject = NSDate()
let formatDate = timeToString(dateObject)
self.creationDate = formatDate
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
self.postUID = userID
self.userName = getUsername(userID)
}
// Used to convert the model to json compatible before saving
func postToDictionary() -> NSDictionary {
let jsonBody = postBody
let jsonDate = creationDate
let jsonUID = postUID
let jsonUsername = userName
let postAsDictionary = ["Body": jsonBody, "Timestamp": jsonDate, "UID": jsonUID, "Display Name": jsonUsername]
return postAsDictionary
}
}
and the function to get the username:
func getUsername(withUID: String) -> String {
var userName = String()
DataService.ref.userRef.childByAppendingPath(withUID).observeSingleEventOfType(.Value, withBlock: { snapshot in
userName = snapshot.value.objectForKey("Display Name") as! String
})
return userName
}
I set up my login function to get the current user's display name and set it to the standardUserDefaults which worked. I believe this is my solution unless someone has a better suggestion
#IBAction func loginButton(sender: AnyObject) {
if emailField != nil && passwordField != nil {
let emailAttempt = emailField.text!
let passwordAttempt = passwordField.text!
DataService.ref.baseRef.authUser(emailAttempt, password: passwordAttempt) {
error, authData in
if error != nil {
print("error in data check")
} else {
let returnUID = authData.uid
NSUserDefaults.standardUserDefaults().setValue(returnUID , forKey: "uid")
DataService.ref.userRef.childByAppendingPath(returnUID).childByAppendingPath("Display Name").observeSingleEventOfType(.Value, withBlock: { snapshot in
let UserDisplayName = snapshot.value as! String
NSUserDefaults.standardUserDefaults().setValue(UserDisplayName, forKey: "displayName")
self.performSegueWithIdentifier("loginSuccessSegue", sender: sender)
})
}
}
} else {
print("error")
return
}
}

Resources