Accessing a variable outside closure Swift3 - ios

I have declared requestPostArray right at the beginning of the ViewController class. I'm trying to populate the requestPostArray from the database "Request Posts" and then populate the tableView from the requestPostArray. However when I print it's size it shows up to be 0. Any help would be appreciated.
Also, the entire else statement below is inside another closure.
else {
ref.child("Request Posts").observe(.value, with: { (snapshot) in
let count = Int(snapshot.childrenCount)
// Request Post database is empty
if count == 0 {
cell.nameLabel.text = "No requests so far."
cell.userNameLabel.isHidden = true
cell.numberOfRequestsLabel.isHidden = true
}
// Request Post data is populated
else {
var requestPostArray: [RequestPost]? = []
self.ref.child("Request Posts").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let post = RequestPost(snapshot: child)
requestPostArray?.append(post)
}
}
else {
print("No Result")
}
})
print("RequestPostArray size = \(requestPostArray?.count ?? 90)")
cell.nameLabel.text = self.requestPostArray?[indexPath.row].name
cell.userNameLabel.text = self.requestPostArray?[indexPath.row].name
cell.numberOfRequestsLabel.text = self.requestPostArray?[indexPath.row].name
}
})
}

observe blocks are asynchronous. You're reading requestPostArray before it has been modified by your requestPostArray?.append(post) line.
Without more context it's hard to know how you want to be populating values on this cell object but if you need "Request Posts" then you have to wait until you've been able to obtain them.

this is what should happens because observe function is asynchronous and the rest of your code is synchronous, in addition if you remove the optional unwrap from requestPostArray? you will get a nil exception because the async task needs time to get executed so the compiler will execute the snyc task before it.
basically what you have to do is the following
else {
ref.child("Request Posts").observe(.value, with: { (snapshot) in
let count = Int(snapshot.childrenCount)
// Request Post database is empty
if count == 0 {
cell.nameLabel.text = "No requests so far."
cell.userNameLabel.isHidden = true
cell.numberOfRequestsLabel.isHidden = true
}
// Request Post data is populated
else {
var requestPostArray: [RequestPost]? = []
self.ref.child("Request Posts").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let post = RequestPost(snapshot: child)
requestPostArray?.append(post)
}
}
else {
print("No Result")
}
print("RequestPostArray size = \(requestPostArray?.count ?? 90)")
cell.nameLabel.text = self.requestPostArray?[indexPath.row].name
cell.userNameLabel.text = self.requestPostArray?[indexPath.row].name
cell.numberOfRequestsLabel.text = self.requestPostArray?[indexPath.row].name
})
}
})
}
another advice think about using singleton so you can gain the reuse of your object and you will not invoke the database several time at the same method like you are doing now.

Related

Parse - JSON text did not start with array or object

I've got this issue when running my code to update a few records. I have 138 records.
I've set limit to get 1000 record per one request and then I've tried to update my new column I've created:
But I get this error when I save PFObject in background
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.
I found this link with similar problem, but looks like that ticket is resolved and closed.
Looking in my code below I am trying to cycle over 138 records to set new key value for PFObject and then I save it. Normally I don't need such operation, maybe this happens because of lot of records were updated at once. But just wonder if this is still the case in api.
This is my code:
let salaryRepository = SalaryDataRepository(remoteDataSource: SalaryRemoteDataSource(), localDataSource: SalaryLocalDataSource())
salaryRepository.getSalaries(with: nil, payPeriod: nil, paidDateDoesNotExist: false, limit: 0, skip: 0, completion: { (result) in
switch result {
case let .success(salaries):
guard let unwrappedSalaries = salaries else {
return
}
var counter = 0
for salary in unwrappedSalaries {
var totalQuote1 = 0.0
if let pfSalary = salary.getPFSalary() {
if let subTotal = pfSalary["subTotal"] as? NSNumber, let unwrappedCustomExchangeRate = pfSalary["customExchangeRate"] as? NSNumber {
if let pfProjectEmployee = pfSalary["projectEmployee"] as? PFObject {
if let pfProjectEmployeeDetails = pfProjectEmployee["projectEmployeeDetails"] as? PFObject {
if let transactionFee = pfProjectEmployeeDetails["transactionFee"] as? NSNumber {
let subTotalQuote = NSNumber.getMultiplying(a: subTotal, b: unwrappedCustomExchangeRate)
let totalQuote = subTotalQuote.afterFee(fee: transactionFee)
pfSalary["totalQuote"] = totalQuote
totalQuote1 = totalQuote.doubleValue
print(transactionFee)
counter = counter + 1
}
}
}
pfSalary.saveInBackground { (success, error) in
if error == nil && success == true {
print("SUCCESS:")
print(totalQuote1)
} else {
print("ERROR:")
print(totalQuote1)
}
}
}
}
}
print("total updated:")
print(counter)
break
case let .failure(error):
print(error)
break
}
})

How can you ensure that cells are not duplicated when the device is overwhelmed by code from the previous page?

I have a problem that only occurs when page A has to run a massive amount of code and the user goes to page B before all the A page code is finished. In these instances, sometimes cells get duplicated(ie, say page B must be : User H in top, user F below him. Instead there are two Hs followed by two Fs below them).
Below is the relevant code of page B, but I am fairly certain the problem does not lie there. Why?: Because I changed the array that gets displayed [H,F] to a set, so according to the code, there should never be an instance like [H,H,F,F]
///Part1: code that gets called from viewdidLoad
var peppi = [Usery]()
func printPersonInfo(uid: String) {
self.handleA = thisUser.observe(DataEventType.value, with: { snapshot in
...
myPeopleRef44.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { [self] snapshot in
let uniqueArray = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray))
for person in peopleArray where uid == person.value as? String {
...
func decode(autoId: String) -> TimeInterval {
}
return TimeInterval(exactly: timestamp)!
}
...
if Calendar.current.isDateInToday(date){
let p = Usery(...)
peppi.append(p)
}
}
DispatchQueue.main.async {
self.peppi.sort { ($0.self.time1 ?? 0) > ($1.self.time1 ?? 0)
}
print(self.peppi, "lo")
self.table.reloadData()
}
})
})
}
/// Part: 2 In viewDidLoad, code that calls the function printPersonInfo
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { snapshot in
func decode(autoId: String) -> TimeInterval {
..
return …
}
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
self.dg.leave()
self.dg.notify(queue: .main) {
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid)
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})

Load data slow while fetching from firebase + iOS

I am preparing iOS native app with using firebase real database.
I have around 100k data.. I need to show recent 50 posts and after load more next 50 posts so I have implemented paging of 50 records still it took long time (approx 20-30 seconds).
ALSO I tried with indexing with rules in firebase.
FYI: It's working fine with 1000 records.
How can I improve the performance?
let post_messagesReference = self.databaseRef.child(FirebaseTablePostMessages)
var query = post_messagesReference.queryOrdered(byChild: kCreated)
var lastKey = String()
var createdVal = Int()
if let last = self.endKeyCommunity.first {
lastKey = last.id
createdVal = Int(last.created)
}
//get first 50 records
if self.endKeyCommunity.count == 0{
query = query.queryLimited(toLast:50)
self.showLoaderView()
} else { //get next 50 records
query = query.queryEnding(atValue: createdVal).queryLimited(toLast: 50)
}
query.observeSingleEvent(of: .value, with: { (snapshot) in
guard let children = snapshot.children.allObjects.first as? DataSnapshot else { return }
if snapshot.childrenCount > 0 {
for child in snapshot.children.allObjects as! [DataSnapshot]{
guard case let communityChatDictionary as NSDictionary = child.value else { return }
if lastKey != child.key{
}
guard case let communityChatDictionary as NSDictionary = children.value else { return }
}
}) { (error) in
}
Try to change your Firebase rules so you set:
".read": "query.orderByKey &&
query.limitToFirst <= 100"
Remove this code var query = post_messagesReference.queryOrdered(byChild: kCreated). Just call your paginated requests like that:
post_messagesReference.limitToFirst(50)
.orderByKey(kCreated)

Completion handler Firebase observer in Swift

I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}

Firebase when query is empty

I perform a query using firebase to check if there are any objects that have a child with a certain value. The problem is if there are no such objects the code doesn't run. So in the code below if there are no objects that have a child with facebookID that is 17 the rest of the code will never run. Is there a way to determine when a query finds 0 results?
func previousUser(completion:(result:Bool)->Void){
var queryFound = false
print("check if user is previous")
print(UserData.globalfacebookID)
let ref = Firebase(url:"https://amber-torch-556.firebaseio.com/Users")
ref.queryOrderedByChild("facebookID").queryEqualToValue(17).observeSingleEventOfType(.ChildAdded, withBlock: { snapshot in
print(snapshot.childrenCount)
print("query has ran")
UserData.globalparseID = snapshot.key
queryFound = true
completion(result:queryFound)
})
Check your ref to make sure it's valid.
and try this
ref.queryOrderedByChild("facebookID").queryEqualToValue(17)
.observeEventType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("dude, snapshot was null")
} else {
print(snapshot.key)
}
})
Change .ChildAdded to .Value and then check for NSNull
To check whether a query returns 0 results, you can check the snapshot as following:
if snapshot.exists() {
// write relevant logic here
}
else {
// handle the case when a query finds 0 results
}
In your case,
func previousUser(completion:(result:Bool)->Void){
var queryFound = false
print("check if user is previous")
print(UserData.globalfacebookID)
let ref = Firebase(url:"https://amber-torch-556.firebaseio.com/Users")
ref.queryOrderedByChild("facebookID").queryEqualToValue(17).observeSingleEventOfType(.ChildAdded, withBlock: { snapshot in
if snapshot.exists() {
print(snapshot.childrenCount)
print("query has ran")
UserData.globalparseID = snapshot.key
queryFound = true
completion(result:queryFound)
}
else {
// handle the case where query finds zero(0) results
}
})
Hope this helps !

Resources