Load data slow while fetching from firebase + iOS - 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)

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)
}
})

Documents fetched are replacing the previous loaded documents in tableview

I am using the below code to fetch the data from the firestore database in swift iOS. But when I scroll the new data loaded is replacing the previously loaded data in the tableview. I am trying to fix this issue but as of now no good.
The outcome required is that adding new documents to the previously list of documents in the tableview
Below is the code I am implementing. If any more information is required please let me know
CODE
fileprivate func observeQuery() {
fetchMoreIngredients = true
//guard let query = query else { return }
var query1 = query
stopObserving()
if posts.isEmpty{
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).limit(to: 5)
}
else {
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).start(afterDocument: lastDocumentSnapshot).limit(to: 2)
// query = db.collection("rides").order(by: "price").start(afterDocument: lastDocumentSnapshot).limit(to: 4)
print("Next 4 rides loaded")
print("hello")
}
// Display data from Firestore, part one
listener = query1!.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Post in
if let model = Post(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
}
self.posts = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
self.fetchMoreIngredients = false
self.lastDocumentSnapshot = snapshot.documents.last
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let off = scrollView.contentOffset.y
let off1 = scrollView.contentSize.height
if off > off1 - scrollView.frame.height * leadingScreensForBatching{
if !fetchMoreIngredients && !reachEnd{
print(fetchMoreIngredients)
// beginBatchFetch()
// query = baseQuery()
observeQuery()
}
}
}
Instead of calling snapshot.documents, call snapshot.documentChanges. This returns a list of document changes (either .added, .modified, or .removed, and allows you to add, remove, or modify them in your local array as needed... Not tested code just an idea what you ca do ...
snapshot.documentChanges.forEach() { diff in
switch diff.type {
case .added:
if let model = Post(dictionary: diff.document.data()){
self.posts.append(model)
}else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
case .removed:
// add remove case
case .modified:
// add modify case
}
}

Getting Four Random, but Unique, Documents from a Cloud Firestore - Swift

Trying to use Dan McGrath's suggested Document Agnostic solution to querying Firestore for random documents, along with the Rinse in Repeat suggestion for pulling multiple random documents.
This code occasionally comes up with nil documents (doesn't always return a document). I think my query is off and am looking for guidance/ideas on how to correct he problem - Thanks
func getRandomPlateOne() {
let plateOneRef = db.collection("plates")
plateOneRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
plateOneRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
EDIT -Though I had this figured out, that passing the random number into a variable, and then using that variable in my query would make certain that the same random number was being used whether the query was going greaterThan or lessThanAndEqualTo. Still getting an occasional nil back from Firestore. My query must still be off.
New code:
func getRandomPlateOne() {
let collectionRef = db.collection("plates")
collectionRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
collectionRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}
var randomNumberOne: UInt64 = 0
EDIT - Function has evolved. I am still unable to get the step between checking if first condition returned a document or not, and moving to a sometimes necessary second query. This works, but I am using a fixed UInt64.
var randomNumberOne: UInt64 = 8190941879098207969 (higher than any other in my collection)
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
print("This is the snapshot from the second query. \(snap!) ")
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
As I said in my above comment, I was using two different random numbers for working up the range of documents, or down the range of documents when necessary.
I created a generateARandomNumber function, that is called in my viewDidLoad function.
func generateARandomNumber() {
randomNumber = UInt64.random(in: 0 ... 9223372036854775807)
}
That number is then passed into a variable, that is used within my getARandomPlate(a Firestore document).
I am now using the same random number, whether searching for a document whose random number isGreaterThan the viewDidLoad generated random number or if I end up querying for a isLessThanOrEqualTo document.
EDIT -
Working code:
let db = Firestore.firestore()
var randomNumberOne: UInt64 = 0
var plates = [Plate]()
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}

Accessing a variable outside closure Swift3

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.

Resources