Firebase setValue method is taking time - ios

I am using firebase in my iOs app.
When I use the setValue method for updating node value, it takes around 5-10 secs to update value.
How can I overcome this delay?
Code
let shiftReportsRef = Database.database().reference().child(Constants.FireBase().users).child(Constants.FireBase().shiftReports).child(firebaseToken).child(cashierId.stringValue).child(todayDate).child(posPointId.stringValue).child(shiftid.stringValue)
shiftReportsRef?.observe(.value, with: { (peakHoursSnapShot) in
self.shiftDetails = peakHoursSnapShot.value as? [String: AnyObject] ?? [:]
let lastUpdated = Date().gmtDateTime(.dateTimeSec)
self.shiftDetails!["lastUpdated"] = lastUpdated as AnyObject
if self.hasUpdatedValues == false
{
self.updatePeakHoursAndLastUpdateDate(amount, refndedAmount, pymtType, timeSlt)
}
self.hasUpdatedValues = true
})

The code in the question is a little unclear as to what the expected outcome is. Let's walk through it...
First shiftReportRef is defined
let shiftReportsRef =
Database.database().reference().child(Constants.FireBase().users).child(Constants.FireBase().shiftReports).child(firebaseToken).child(cashierId.stringValue).child(todayDate).child(posPointId.stringValue).child(shiftid.stringValue)
then an an observer is added to that ref. The observer is .value which will observe any changes to that node and return them in a snapshot via an event
shiftReportsRef?.observe(.value
But then when the first event occurs and the code within the closure executes, the data at that location is overwritten
shiftReportsRef?.setValue(self.shiftDetails)
which then causes another .value event to fire, therefore overwriting the node again (I think you can see where this is going). Over and over.
So this isn't exactly an answer because we don't know the expected outcome but that's probably why there are delays in the app.

Related

Firebase query observing reshowing data

I have a firebase query that observes data from a posts child.
func fetchPosts () {
let query = ref.queryOrdered(byChild: "timestamp").queryLimited(toFirst: 10)
query.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let value = child.value as? NSDictionary {
let post = Post()
let poster = value["poster"] as? String ?? "Name not found"
let post_content = value["post"] as? String ?? "Content not found"
let post_reveals = value["Reveals"] as? String ?? "Reveals not found"
post.post_words = post_content
post.poster = poster
post.Reveals = post_reveals
self.postList.append(post)
DispatchQueue.main.async { self.tableView.reloadData() }
//make this for when child is added but so that it also shows psots already there something like query.observre event type of
}
}
However, when a user posts something, it creates a more than one cell with the data. For instance, if I post "hello", a two new cards show up with the hello on it. However, when I exit the view and recall the fetch posts function, it shows the correct amount of cells. Also, when I delete a post from the database, it adds a new cell as well and creates two copies of it until I reload the view, then it shows the correct data from the database.
I suspect this has something to do with the observe(.value), as it might be getting the posts from the database and each time the database changes it creates a new array. Thus, when I add a new post, it is adding an array for the fact that the post was added and that it now exists in the database, and when I refresh the view it just collects the data directly from the database.
Also, sometimes the correct amount of cells show and other times there's multiple instances of random posts, regardless of whether I have just added them or not.
How can I change my query so that it initially loads all the posts from the database, and when some post is added it only creates one new cell instead of two?
Edit: The logic seeming to occur is that when the function loads, it gets all the posts as it calls the fetchPosts(). Then, when something is added to the database, it calls the fetchPosts() again and adds the new data to the array while getting all the old data. yet again.
One thing I always do when appending snapshots into an array with Firebase is check if it exists first. In your case I would add
if !self.postList.contains(post) {
self.postList.append...
however, to make this work, you have to make an equatable protocol for what I'm guessing is a Post class like so:
extension Post: Equatable { }
func ==(lhs: Post, rhs: Post) -> Bool {
return lhs.uid == rhs.uid
}
You are right in thinking that the .value event type will return the entire array each time there is a change. What you really need is the query.observe(.childAdded) listener. That will fetch individual posts objects rather than the entire array. Call this in your viewDidAppear method.
You may also want to implement the query.observe(.childRemoved) listener as well to detect when posts are removed.
Another way would be to call observeSingleEvent(.value) on the initial load then add a listener query.queryLimited(toLast: 1).observe(.childAdded) to listen for the latest post.

Issues reading data from Firebase Database

Okay I am reading from a database and when I print the individual variables they print out correctly. However it seems like the data refuses to append to the array. Anyone know why? I can't figure it out at all.
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
//print(snapshot)
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
})
print("helper")
print(commuteArray.count)
return commuteArray
The data is correctly added to the array, just not at the time that you print the array's contents.
If you change the code like this, you can see this:
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
print("added one, now have \(commuteArray.count)")
})
print("returning \(commuteArray.count)")
return commuteArray
You'll see it print something like this:
returning 0
added one, now have 1
added one, now have 2
etc.
This is likely not the output you expected. But it is working as intended. Firebase loads data from its database asynchronously. Instead of blocking your code, it lets the thread continue (so the user can continue using the app) and instead calls back to the code block you passed to observe when new data is available.
This means that by the time this code returns the array it is still empty, but it later adds items as they come in. This means that you cannot return data from a function in the way you are trying.
I find it easiest to change my way of thinking about code. Instead of "First get the data, then print it", I frame it as "Start getting the data. When data comes back, print it".
In the code above, I did this by moving the code that prints the count into the callback block. Instead of doing this, you can also create your own callback, which is called a completion handler or closure in Swift. You can find examples in this article, this article, this question Callback function syntax in Swift or of course in Apple's documentation.

How to Update uitableview from CloudKit using discoverAllIdentities

So, I am new to cloudKit and to working with multiple threads in general, which I think is the source of the problem here, so if I simply need to research more, please just comment so and I will take that to heart.
Here is my question:
I am working in Swift 3 Xcode 8.1
I have in my view controller this variable:
var contactsNearby: [String:CLLocation]?
Then at the end of ViewDidLoad I call one of my view controllers methods let's call it:
populateContactsNearby()
inside that method I call:
container.discoverAllIdentities(completionHandler: { (identities, error) in
for userIdentity in identities! {
self.container.publicCloudDatabase.fetch(withRecordID: userIdentity.userRecordID!, completionHandler: { (userRecord, error) in
let contactsLocation = userRecord?.object(forKey: "currentLocation")
if self.closeEnough(self.myLocation!, contactLocation: contactsLocation as! CLLocation) {
var contactsName = ""
contactsFirstName = userIdentity.nameComponents?.givenName
if contactsName != "" && contactsLocation != nil {
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
}
}
})
}
})
}
I apologize if I am missing or have an extra bracket somewhere. I have omitted some error checking code and so forth in order to get this down to bare-bones. So the goal of all that is to populate my contactsNearby Dictionary with data from CloudKit. A name as the key a location as the value. I want to use that data to populate a tableview. In the above code, the call to closeEnough is a call to another one of my view controllers methods to check if the contact from CloudKit has a location close enough to my user to be relevant to the apps purposes. Also myLocation is a variable that is populated before the segue. It holds the CLLocation of the app users current location.
The Problem:
The if statement:
if contactsName != "" && contactsLocation != nil { }
Appears to succeed. But my view controllers variable:
var contactsNearby: [String:CLLocation]?
Is never populated and I know there is data available in cloudKit.
If it's relevant here is some test code that I have in cellForRowAtIndexPath right now:
let contact = self.contactsNearby?.popFirst()
let name = contact?.key
if name != nil {
cell.textLabel?.text = name
}else {
cell.textLabel?.text = "nothing was there"
}
My rows alway populate with "nothing was there". I have seen answers where people have done CKQueries to update the UI, but in those answers, the user built the query themselves. That seems different from using a CloudKit function like discoverAllIdentities.
I have tried to be as specific as possible in asking this question. If this question could be improved please let me know. I think it's a question that could benefit the community.
Okay, I need to do some more testing, but I think I got it working. Thank you Paulw11 for your comment. It got me on the right track.
As it turns out there were 2 problems.
First, as pointed out I have an asynchronous call inside a for loop. As recommended, I used a dispatchGroup.
Inside the cloudKit call to discoverAllIdentities I declared a dispatchGroup, kind of like so:
var icloudDispatchGroup = DispatchGroup()
Then just inside the for loop that is going to make an async call, I enter the dispatchGroup:
icloudDispatchGroup.enter()
Then just before the end of the publicCloudDatabase.fetch completion handler I call:
icloudDispatchGroup.leave()
and
icloudDispatchGroup.wait()
Which, I believe, I'm still new to this remember, ends the dispatchGroup and causes the current thread to wait until that dispatchGroup finishes before allowing the current thread to continue.
The Above took care of the multithreading issue, but my contactsNearby[String:CLLocation]? Dictionary was still not being populated.
Which leads me to the 2nd problem
At the top of my view controller I declared my Dictionary:
var contactsNearby: [String: CLLocation]?
This declared a dictionary, but does not initialize it, which I don't think I fully realized, so when I attempted to populate it:
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
It quietly failed because it is optional and returned nil
So, in viewDidLoad before I even call populateContactsNearby I initialize the dictionary:
contactsNearby = [String:CLLocation]()
This does not make it cease to be an optional, which Swift being strongly typed would not allow, but merely initializes contactsNearby as an optional empty Dictionary.
At least, that is my understanding of what is going on. If anyone has a more elegant solution, I am always trying to improve.
In case you are wondering how I then update the UI, I do so with a property observer on the contactsNearby Dictionary. So the declaration of the dictionary at the top of the view controller looks like this:
var contactsNearby: [String: CLLocation]? {
didSet {
if (contactsNearby?.isEmpty)! || contactsNearby == nil {
return
}else{
DispatchQueue.main.sync {
self.nearbyTableView.reloadData()
}
}
}
}
I suppose I didn't really need to check for empty and nil. So then in cellForRowAtIndexPath I have something kind of like so:
let cell = tableview.dequeueReusableCell(withIdentifier: "nearbyCell", for: indexPath)
if contactsNearby?.isEmpty == false {
let contact = contactsNearby?.popFirst()
cell.textLabel?.text = contact?.key
}else {
cell.textLabel?.text = "Some Placeholder Text Here"
}
return cell
If anyone sees an error in my thinking or sees any of this heading for disaster, feel free to let me know. I still have a lot of testing to do, but I wanted to get back here and let you know what I have found.

Receiving Data using Firebase swift

I am trying to retrieve Childs from The Firebase Database using this code (which is declared in viewdidload)
let rootRef = FIRDatabase.database().reference().child("UsersInfo").child((FIRAuth.auth()?.currentUser?.uid)!)
rootRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
let hello = snapshot.value as! [String : AnyObject]
let usernamerecieved = hello["Username"] as! String
let Emailrecieved = hello["Email"] as! String
let bloodtyperecieved = hello["BloodType"] as! String
globalusername = usernamerecieved (EDITED)
})
and I've declared a global variable as such
var globalusername = "user"
Im trying to extract the usernamerecieved variable and casting it on a Global Variable. However when I print the global variable later on in the viewdidLoad I still get the initial value of the globalusername which is "user".
In this line of code : usernamerecieved = globalusername what you are doing in this line is that you are setting the value of the usernamerecieved to the value of globalusername i.e. 'user'
It should be globalusername = usernamerecieved.
Also sometimes it takes time to retrieve data from the server reasons could be numerous - slow network connection, heavy data retrieving such as images etc. but before it could be completed (Retrieving Action From The Server) it might be possible that line in which you are printing the globalusername gets called first , even before the completionBlock off the observeEventType is even completed (since its asynchronous , somewhat similar to dispatch_async).To tackle that you should call the printing call for globalusername inside the completionBlock or observe any change in it's value by using addObserver to the globalusername,which will let you know whenever the value of globalusername is changed or updated so that you can act accordingly .
observeEventType's block is asynchronous, it is possible that by the time you read the globalusername var, the block has not been completed yet.
Try printing globalusername inside the block and see the value

How can I get results for both existing and new children in Firebase?

I'm trying to create a query in Firebase for my swift iOS app. The problem I'm having with the query is that it does not get the coordinate in the immediately from firebase, unless they are changed. I tried every other observer type, but none appear to work. I know I currently have the observer type to change, but I need the correct way to make it get the location as soon as the app is loaded and also update with firebase. .childAdded gets location immediately but it does not update when they are changed on Firebase.
userDirectory.queryOrderedByChild("receiveJobRequest")
.queryEqualToValue(1)
.observeEventType(.ChildChanged , withBlock: {snapshot in
var cIhelperslatitude = snapshot.value["currentLatitude"]
var cIhelperslongitude = snapshot.value["currentLongitude"]
If you want to listen to multiple event types, you'll need to register multiple listeners.
let query = userDirectory.queryOrderedByChild("receiveJobRequest")
.queryEqualToValue(1)
query.observeEventType(.ChildAdded, withBlock: {snapshot in
var cIhelperslatitude = snapshot.value["currentLatitude"]
var cIhelperslongitude = snapshot.value["currentLongitude"]
query.observeEventType(.ChildChanged, withBlock: {snapshot in
var cIhelperslatitude = snapshot.value["currentLatitude"]
var cIhelperslongitude = snapshot.value["currentLongitude"]
You'll probably want to refactor that common code into a method, that you them call from the .ChildAdded and .ChildChanged blocks.
Alternatively you can register for a .Value event, which is triggered for the initial value and every time the value under the query is changed. But since .Value is invoked with all matching children, you'll have to then loop over the children in your block:
query.observeEventType(.Value, withBlock: {allsnapshot in
for snapshot in allsnapshot.children {
var cIhelperslatitude = snapshot.value["currentLatitude"]
var cIhelperslongitude = snapshot.value["currentLongitude"]
}

Resources