I'm building a student-tutor app using swift + firebase (auth and firestore).
I have a wrapper widget that checks if a user is logged in. If they're not, I direct them to an authentication screen (login/registration). If they are logged in, I then want to check if they are a student or a tutor.
In other words, in my wrapper, I need a way to retrieve user data from firestore and check their role and then direct them to the appropriate screen. I can't figure out how to do it. Please help. This is my wrapper class
let UID = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
db.collection("Users").document(UID!).getDocument { snapshot, error in
if error == nil {
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "SignUpViewControllerID")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}else{
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "MainTabBarController") {
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}
let db = Firestore.firestore()
db.collection("Users").document(UID!).getDocument { snapshot, error in
if error != nil {
for document in snapshot.documents {
let UID = document.documentID
}
}
}
You can access the uid in different viewController via two ways.
i) Make a function and pass the uid, call the function in another viewController.
ii) let userId = Auth.auth().currentUser?.uid , by this term( You have to import the FireBaseAuth).
Related
This is my firebase database path ->ID/uid/Profile/Safety-Check
The way I want to achieve is this
key: Safety-Check,
value: "admin" , go to ViewController "adminVC",
Value: "ON" , go to ViewController "MainTabBarController",
Without this key: Safety-Check , go to ViewController "SignUpViewControllerID"
Below is my current code but it doesn't work,No KEY "Safety-Check" program crashes, my KEY "Safety-Check" is generated in next ViewController "SignUpViewControllerID", hope someone can give me some advice, thanks
Database.database().reference().child("ID/\(self.uid)/Profile/Safety-Check").observeSingleEvent(of: .value, with: {
(snapshot) in
if error != nil {
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "SignUpViewControllerID")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}else{
switch snapshot.value as! String {
// If our user is admin...
case "admin":
// ...redirect to the admin page
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "admin")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
// If out user is a regular user...
case "ON":
// ...redirect to the user page
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "MainTabBarController")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
// If the type wasn't found...
default:
print("Error: Couldn't find type for user ")
This is an example picture for explaining a problem in my project.
I have a tabbed view controller here, in one tab i have Guide Profile List, in other tab i have List of Chats i have done.
There is a single chatScreenViewController.. (example picture like this)
this single chat view controller loads up in 2 ways,
(1) when i click any conversation from list of conversations in chats tab.
(2) In guide profile screen there is a Chat button, when i click that chat button which instantiate a singleChatViewController and loads up in navigation stack.
whenever this view controller is loaded this function is called, i.e. this function adds a listner to a document of firebase so that it may listen to any messages that are sent.
func loadUpChatModel()
{
guard let chateeInfo = self.chateeInfoStruct
else
{
print("chateeInfoStruct from previous vc is not set")
return
}
let chatId = chateeInfo.chatId
setTouristReadValueToTrue(chatId: chatId)
let chatRef = Firestore.firestore().collection("chats").document(chatId)
self.listner = chatRef.addSnapshotListener
{ (chatDocument, error) in
if let err = error
{
print(err.localizedDescription)
print("error getting a chat document")
}
else
{
if chatDocument != nil, chatDocument!.exists
{
guard let data = chatDocument!.data()
else
{
print("unable to convert data of single chat for chat list model")
return
}
if let model = self.chatModel
{
let oldTotalMessages = model.totalMessages!
let newTotalMessages = data["totalMessages"] as! Int
let messagesAdded = newTotalMessages - oldTotalMessages
var i = 0
while i < messagesAdded
{
let message = data["message\(oldTotalMessages+i+1)"] as? String ?? "Unable to retrieve"
let messageSentStatus = data["message\(oldTotalMessages+i+1)sent"] as? Bool ?? false
let block = messageBlock(message: message, messageWasSent: messageSentStatus)
self.chatModel!.messagesArray.append(block)
i += 1
}
self.chatModel!.lastMessageDate = data["lastMessageDate"] as? String ?? "date not found for this chat"
self.chatModel!.totalMessages = newTotalMessages
self.updateTableView()
}
else
{
let lastMessageDate = data["lastMessageDate"] as? String ?? "date not found for this chat"
let totalMessages = data["totalMessages"] as? Int ?? 0
let singleChatModel = SingleChatDM(chatId: chatId, lastMessageDate: lastMessageDate, totalMessages: totalMessages)
var i = 0
while i < totalMessages
{
let message = data["message\(i+1)"] as? String ?? "Unable to retrieve"
let messageSentStatus = data["message\(i+1)sent"] as? Bool ?? false
let block = messageBlock(message: message, messageWasSent: messageSentStatus)
singleChatModel.messagesArray.append(block)
i += 1
}
self.chatModel = singleChatModel
self.updateTableView()
}
}
}
}
}
If i open a single chat in one tab, it works fine, messages are sent and received and table view loads correctly.
BUT, when i load this view controller in other tab also by clicking on chat button from same guide profile whose single chat is loaded in other tab, the problem arise.
Now when i send the message the data is correctly stored to firebase document but my table view is not updated, i cannot see the new messages i sent in both of the view controllers. (This is very much according to me is because i am not receiving any updated document snapshots i.e. listener is not working).
any help about this behaviour!! I searched for but i could not found anything useful.
You can check for an active view controller by a super global variable, if that variable is set, then instead of again loading that view controller make that tab active in which your chat view controller is already loaded. Not a permanent solution but i think this may work !!
In my main VC I look for changes in my FB database like this:
ref.child("posts").observe(.childChanged, with: { (snapshot) in
.......
})
From this VC I can enter VC2 which is set to be "present modally" in my segue.
Now I wonder if I can pass live FB data from VC1 to VC2? I know that I can use a segue.identifier and pass data when I segue to the next VC but this is one time send only. Or should I setup a delegate to fetch data from vc1 to vc2?
So is there any way I can send data from VC1 to VC2 once a node has been updated or must I setup a new .observe() function in VC2?
First I would like to remind you about the singleton design patter :
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
So the first thing you need to do is to create a call that contains as a parameters the data you get from firebase, I have did the following to get the following in order to get the user data when he logged in into my app and then use these data in every part of my application (I don't have any intention to pass the data between VC this is absolutely the wrong approach )
my user class is like this :
import Foundation
class User {
static let sharedInstance = User()
var uid: String!
var username: String!
var firstname: String!
var lastname: String!
var profilePictureData: Data!
var email: String!
}
after that I have created another class FirebaseUserManager (you can do this in your view controller but it's always an appreciated idea to separate your view your controller and your model in order to make any future update easy for you or for other developer )
So my firebaseUserManager class contains something like this
import Foundation
import Firebase
import FirebaseStorage
protocol FirebaseSignInUserManagerDelegate: class {
func signInSuccessForUser(_ user: FIRUser)
func signInUserFailedWithError(_ description: String)
}
class FirebaseUserManager {
weak var firebaseSignInUserManagerDelegate: FirebaseSignInUserManagerDelegate!
func signInWith(_ mail: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: mail, password: password) { (user, error) in
if let error = error {
self.firebaseSignInUserManagerDelegate.signInUserFailedWithError(error.localizedDescription)
return
}
self.fechProfileInformation(user!)
}
}
func fechProfileInformation(_ user: FIRUser) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let currentUid = user.uid
ref.child("users").queryOrderedByKey().queryEqual(toValue: currentUid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
let dict = snapshot.value! as! NSDictionary
let currentUserData = dict[currentUid] as! NSDictionary
let singletonUser = User.sharedInstance
singletonUser.uid = currentUid
singletonUser.email = currentUserData["email"] as! String
singletonUser.firstname = currentUserData["firstname"] as! String
singletonUser.lastname = currentUserData["lastname"] as! String
singletonUser.username = currentUserData["username"] as! String
let storage = FIRStorage.storage()
let storageref = storage.reference(forURL: "gs://versus-a107c.appspot.com")
let imageref = storageref.child("images")
let userid : String = (user.uid)
let spaceref = imageref.child("\(userid).jpg")
spaceref.data(withMaxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
// Uh-oh, an error occurred!
print(error.localizedDescription)
} else {
singletonUser.profilePictureData = data!
print(user)
self.firebaseSignInUserManagerDelegate.signInSuccessForUser(user)
}
}
}
})
}
}
so basically this class contains some protocols that we would implements and two functions that manager the firebase signIn and fechProfileInformation , that will get the user information
than in my login View controller I did the following :
1 implement the protocol
class LoginViewController: UIViewController, FirebaseSignInUserManagerDelegate
2 in the login button I did the following
#IBAction func loginAction(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text else { return }
let firebaseUserManager = FirebaseUserManager()
firebaseUserManager.firebaseSignInUserManagerDelegate = self
firebaseUserManager.signInWith(email, password: password)
}
3 implement the protocol method :
func signInSuccessForUser(_ user: FIRUser) {
// Do something example navigate to the Main Menu
}
func signInUserFailedWithError(_ description: String) {
// Do something : example alert the user
}
So right now when the user click on the sign in button there is an object created which contains the user data save on firebase database
now comes the funny part (the answer of your question : how to get the user data in every where in the app)
in every part of my app I could make
print(User.sharedInstance.uid) or print(User.sharedInstance. username)
and I get the value that I want to.
PS : In order to use the singleton appropriately you need to make sure that you call an object when it's instantiated.
This is my table created in Firebase here. I have a search button. The button action will be at first it will fetch data from firebase and then it will send it to another view controller and will show it in a table view. but main problem is that before fetching all data from Firebase
self.performSegueWithIdentifier("SearchResultPage", sender: self )
triggers and my next view controller shows a empty table view. Here was my effort here.
From this post here I think that my code is not placed well.
Write this line of code in your dataTransfer method in if block after complete for loop
self.performSegueWithIdentifier("SearchResultPage", sender: self )
Try sending the search string as the sender when you are performing the segue, for example:
self.performSegueWithIdentifier("SearchResultPage", sender: "searchString")
Then, in your prepare for segue get the destination view controller and send over the string to the new view controller. something like:
destinationViewController.searchString = sender as! String
When the segue is completed and you have come to the new view controller, you should now have a searchString value that is set, use this value to perform your query and get the relevant data to show in the new table view.
Please note that this is just one solution of many, there are other ways to achieve this.
The reason why you are not able to send data is because, you are trying to send data even before the data could actually be fetched.
Try the following where you are pushing to next view controller only after getting the data
func dataTransfer() {
let BASE_URL_HotelLocation = "https://**************.firebaseio.com/Niloy"
let ref = FIRDatabase.database().referenceFromURL(BASE_URL_HotelLocation)
ref.queryOrderedByChild("location").queryStartingAtValue("uttara").observeEventType(.Value, withBlock: { snapshot in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let downloadURL = child.value!["image"] as! String;
self.storage.referenceForURL(downloadURL).dataWithMaxSize(25 * 1024 * 1024, completion: { (data, error) -> Void in
let downloadImage = UIImage(data: data!)
let h = Hotel()
h.name = child.value!["name"] as! String
print("Object \(count) : ",h.name)
h.deal = child.value!["deal"] as! String
h.description = child.value!["description"] as! String
h.distance = child.value!["distance"] as! String
h.latestBooking = child.value!["latestBooking"] as! String
h.location = child.value!["location"] as! String
h.image = downloadImage!
self.HotelObjectArray.append(h)
})
}
let storyboard = UIStoryboard(name:"yourStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SearchResultPageIdentifier") as! SearchResultPage
self.navigationController.pushViewController(vc, animated: true)
}
else {
print("no results")
}
})
}
In my app I have a simple user base that looks like this:
What I'm trying to do is to simply fetch this list once, to check wether a username is valid when a new user signs up with a new username.
The thing is that the only ways I found to retrieve data utilize some sort of observer methods, which are not good for me.
The logic I'm trying to achieve (with the retrieving method that doesn't work) :
// When user tries to sign up with a new username
let username = nicknameField.text?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
self.usersRef.observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
}
}
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
How can I simply just fetch the list of users once?
Thanks in advance!
I had to change the observeEventType method to observeSignleEventOfType.
I have also updated my code to make it work (regardless):
self.usersRef.observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
else {
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
}