Firebase logged in user not loading fast enough before segue - ios

I'm hitting this button
override func viewDidLoad() {
FIRAuth.auth()?.signIn(withEmail: "hakuna.matata.kitty#gmail.com", password: "password") {
(user, error) in
// TODO: SET UP A SPINNER ICON
print("Logged into hmk")
self.performSegue(withIdentifier: "login", sender: self)
}
}
As you can see, on callback it redirects to a UITableViewController. The populator code sits in the initializer of UITableViewController, the sole purpose in the closure is to populate self.chatsList
Sometimes, whenever I test out the app, the UITableViewController is blank.
ref.child("users").child(currentUser.uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard snapshot.exists() else {
print("Error: Path not found")
return
}
let value = snapshot.value as? NSDictionary
// Convert a dict of dicts into an array of dicts
// You will lose chatID in the process, so be warned
// Unless you would like to to keep the data in a struct
for (chatIdKey, secondDict) in value?["chats"] as! [String: NSDictionary] {
// This only appends metadata for the last chat
// Does not load every chat message
self.chatsList.append(Chat(chatId: chatIdKey, targetChat: secondDict))
}
})
But when I hit the back key, wait long enough, and login again, the table is populated properly.
How can I make sure that it loads correctly every time? I expected the performSegue on callback would guarantee that we do have a currentUser...

I think your observer should be changed. I see this tableview will be some kind of chat so you might want to keep the reference alive while the user is in that ViewController so instead of using observeSingleEvent you could just observe and you manually remove the observer when leaving the view.
Another thing is that you are observing the value when you should probably observe the childAdded as if any event is added while being in the "chat" view the user will not receive it.
Change the line below and see if this works for you.
ref.child("users").child(currentUser.uid).observe(.childAdded, with: { (snapshot) in
Hope it helps!

Related

Firebase iOS -Key/Value pair isn't physically deleting from the database

I hold all the usernames inside a separate node to run searches on when users search for a username. I deleted a name from the node eg. pizzaMan. The problem is even though it deletes, if I run a search on the deleted name from within my app it says it's available but when I physically look inside the database it shows it's still physically there (meaning it shouldn't be available). How is that possible?
#IBAction func deleteUsernameButtonTapped(_ sender: UIButton) {
// the user's username is pizzaMan
let username = usernamesRef?.child("pizzaMan")
userName?.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
let key = snapshot.key
username?.child(key).removeValue()
print("username: \(key) has been deleted\n")
}
})
}
The username pizzaMan has been deleted but physically inside the database it shows it's still there.
let checkUsernameTextField = UITextField()
checkUsernameTextField.addTarget(self, action: #selector(handleSearchForUsername), for: .editingChanged)
#objc func handleSearchForUsername() {
// now search for pizzaMan inside a textField
usernamesRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("name is NOT avail")
} else {
print("name IS available") // this prints when searching for pizzaMan even though it's inside the db??
}
})
}
If I try to obtain it lets me and just writes over the old value with whatever the new value is but it still shouldn't show up inside the database once removed.
Your problem lies inside your delete func if I am understanding your issue.
Let me reiterate what I think you are saying. You want pizzaMan's node to be completely removed, yes? Try this:
#IBAction func deleteUsernameButtonTapped(_ sender: UIButton) {
// the user's username is pizzaMan
if let username = usernamesRef as? DatabaseReference{
username.child("usernames/pizzaMan").removeValue()
}
else{
print("Errors")
}
}
Is that what you intend to do?

Firebase, how observe works?

I honestly I have tried to figure out when to call ref.removeAllObservers or ref.removeObservers, but I'm confused. I feed I'm doing something wrong here.
var noMoreDuplicators = [String]()
func pull () {
if let myIdi = FIRAuth.auth()?.currentUser?.uid {
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in
if let userers = snapshot.value as? [String : AnyObject] {
for (_, velt) in userers {
let newUser = usera()
if let thierId = velt["uid"] as? String {
if thierId != myIdi {
if let userName = velt["Username"] as? String, let name = velt["Full Name"] as? String, let userIdent = velt["uid"] as? String {
newUser.name = name
newUser.username = userName
newUser.uid = userIdent
if self.noMoreDuplicators.contains(userIdent) {
print("user already added")
} else {
self.users.append(newUser)
self.noMoreDuplicators.append(userIdent)
}
}
}
}
}
self.tableViewSearchUser.reloadData()
}
})
ref.removeAllObservers()
}
}
Am I only supposed to call removeAllObservers when observing a single event, or...? And when should I call it, if call it at all?
From official documentation for observe(_:with:) :
This method is used to listen for data changes at a particular location. This is
the primary way to read data from the Firebase Database. Your block
will be triggered for the initial data and again whenever the data
changes.
Now since this method will be triggered everytime the data changes, so it depends on your usecase , if you want to observe the changes in the database as well, if not then again from the official documentation:
Use removeObserver(withHandle:) to stop receiving updates.
Now if you only want to observe the database once use observeSingleEvent(of:with:) , again from official documentation:
This is equivalent to observe:with:, except the block is
immediately canceled after the initial data is returned
Means that you wont need to call removeObserver(withHandle:) for this as it will be immediately canceled after the initial data is returned.
Now lastly , if you want to remove all observers , you can use this removeAllObserver but note that:
This method removes all observers at the current reference, but does
not remove any observers at child references. removeAllObservers must
be called again for each child reference where a listener was
established to remove the observers
Actually, you don't need to call removeAllObservers when you're observing a single event, because this observer get only called once and then immediately removed.
If you're using observe(.value) or observe(.childAdded), and others though, you would definitely need to remove all your observers before leaving the view to preserve your battery life and memory usage.
You would do that inside the viewDidDisappear or viewWillDisappear method, like so:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove your observers here:
yourRef.removeAllObservers()
}
Note: you could also use removeObserver(withHandle:) method.

Who came first? IBAction or ViewDidLoad

I have a Button on First VC which is directed to two active states.
1) SecondVC
override func viewDidLoad() {
super.viewDidLoad()
subjectPickerView.dataSource = self
subjectPickerView.delegate = self
SwiftyRequest()
// Used the text from the First View Controller to set the label
}
func SwiftyRequest(){
print("SecondViewController METHOD BEGINS")
let jsonobj = UserDefaults.standard.object(forKey: "PostData")
let json = JSON(jsonobj as Any)
for i in 0 ..< json.count{
let arrayValue = json[i]["name"].stringValue
print(arrayValue)
self.subjects.append(arrayValue)
self.subjectPickerView.reloadAllComponents()
}
print(self.subjects)
}
2) IBAction of FirstVC
#IBAction func buttonPressed(_ sender: Any) {
Alamofire.request("http://localhost/AIT/attempt3.php",method: .post, parameters: ["something": semValue, "branch" : streamValue])
.responseJSON { response in
print(response.result)
if let JSON1 = response.result.value {
print("Did receive JSON data: \(JSON1)")
// JSONData.someData = JSON1 as AnyObject?
UserDefaults.standard.set(JSON1, forKey: "PostData")
UserDefaults.standard.synchronize()
}
else {
print("JSON data is nil.")
}
}
}
NOW, Whenever i pressed the button it calls the viewDidLoad of SecondVC before IBAction of FirstVC which is a bit problematic for my app! How can i decide the priority between these two function.
You have to think about what you want to happen. Clearly the Alamofire call is going to take some time. What do you want to do with the 2nd VC while that time elapses? What do you want to do if the call does not return at all?
This is a common problem when dependent on external resources. How do you manage the UI? Do you present the UI in a partial state? Do you put a popover saying something like "loading". Or do you wait for the resource to complete before presenting the 2nd VC at all?
We cannot make that decision for you, since it depends on your requirement. There are ways to implement each one, though. If the resource usually responds quickly you could show the VC in a partial state and then populate it on some kind of callback. Typically call backs are either (1) blocks (2) delegate methods or (3) notifications. There is also (less commonly) (4) KVO. You should probably research the pros and cons of each.

Passing data from completion block to view controller swift 3

!I have been stuck for 2 days on this issue. Here is my scenario.
User clicks login button, if email address and password are stored in UserDefaults, the app silently login the user in, first presenting an UIAlertController while retrieving the user's data from the server. Once the data is returned to the app, a completion handler assigns a local property var User: [String: AnyObject] the results. I can print(self.User) at this point and the data is properly assigned. All good. The problem occur when I try to pass this data to the next controller that I am presenting via present. Here is my code:
LoginViewController.swift
-----
dismiss(animated:true, completion: {
let tenantViewController = storyboard.instantiateViewController(withIdentifier :"tenantDashboardViewController") as! TenantDashboardViewController
tenantViewController.User = user
print(tenantViewController.User) //Works
self.present(tenantViewController, animated: true)
})
Here is my destination viewcontroller
import Foundation
import SideMenuController
class TenantDashboardViewController: SideMenuController {
var User: [String: AnyObject]?
override func viewDidLoad() {
super.viewDidLoad()
performSegue(withIdentifier: "showTenantDashboardHomeViewController", sender: nil)
performSegue(withIdentifier: "containTenantSideMenu", sender: nil)
print(self.User!) //Always returns nil, crashes app
}
}
Thanks in advance!
If you're storing a user object app-side, you should store the User Object in NSUserDefaults. Make it NSCoding compliant so you can encode/decode the User class as you need to.
Then, as Matthew states, store your username/password combo in Keychain, because it is extremely sensitive and Keychain is designed to protect your users' credentials in a secure way.

Why doesn't my If-Else block work the way it should?

I'm new to swift and using firebase, I'm making a simple application that let's user input a cellular prefix number and displays what cellular network is that number.
I'M having a hard problems with the if-else part. Whenever I add the else part, the label always displays "Missing" but whenever I print the snapshot.key and snapshot.value, I get the correct result in the console. When I remove the else part, it works. I'm really having a hard time figuring out the answer. Thanks in advance! Here is the code:
import UIKit
import Firebase
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
let loginButton: FBSDKLoginButton = {
let button = FBSDKLoginButton()
button.readPermissions = ["email"]
return button
}()
#IBOutlet weak var numField: UITextField!
#IBOutlet weak var answerField: UILabel!
#IBAction func enterButton(sender: AnyObject) {
matchNumber()
}
func matchNumber(){
let number: String = numField.text!
let numRef = Firebase(url: "https://npi2.firebaseio.com/num_details")
numRef.queryOrderedByKey().observeEventType(.ChildAdded, withBlock: { snapshot in
let network = snapshot.value as! String!
if snapshot.key == self.numField.text! {
self.answerField.text = network
}
else {
self.answerField.text = "Missing"
}
})
}
I think I know what is happening.
EXPLANATION:
1) You make your request to Firebase.
2) The request is asynch, so the code keeps running.
3) The request is not done yet, therefore the else statement is executed because the value is not yet returned (checks for initial state).
4) When you remove the else, the code executes the if because there is nothing else to execute until the request is done (initial state is checked but there is no code to execute for it, then Firebase checks again because there is a data change and the if statement is executed).
REFERENCE:
From the FIREBASE DOCUMENTATION (that you should have read before coming here :P)
"Firebase data is retrieved by attaching an asynchronous listener to a database reference. The listener will be triggered once for the initial state of the data and again anytime the data changes. This document will cover the basics of retrieving data, how data is ordered, and how to perform simple queries on data in your Firebase database."
https://www.firebase.com/docs/ios/guide/retrieving-data.html
SOLUTION:
Use the completion handler. It's made for that. Don't use else statements inside your request like that. That's not how you use Firebase.

Resources