Parse - JSON text did not start with array or object - ios

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

Related

Failed to get the 'some' field from optional

astoundingly, typing this error into the Stack Overflow search returns no results that actually mention this error 0.0
So I have an Event object in my Swift IOS App which is saved as a document on Firestore that looks like this
The start and end fields are Timestamps.
Over on xcode when the Event collection is queried, the results are decoded into Events with this initialiser
init(document: DocumentSnapshot) {
self.id = document.documentID
let d = document.data()
self.title = d!["title"] as? String
let stamp = d!["start"] as? Timestamp
let estamp = d!["end"] as? Timestamp
self.start = stamp?.dateValue()
self.end = estamp?.dateValue()
/*
There is a breakpoint here!
*/
self.creator = d!["user"] as? String
self.isAllDay = (d!["isAllDay"] as? Bool)!
self.isPrivate = d!["isPrivate"] as! Bool
self.count = (d!["count"] as? String)!
self.date = d?["day"] as? String
self.month = d?["month"] as? String
self.year = d?["year"] as? String
self.bridgesDays = doesEventBridgeDays()
//MARK: re-implement these functions
isInvitee()
}
I've just swapped this over from using Strings to Timestamps and i now have unexpectedly found nil errors for the start and end fields on the app.
a breakpoint shows me this:
As you can see, the start and end fields now say Failed to get the 'some' field from optional start/end (start and end are now both Date objects)
I either don't understand what i'm reading online, or there are no questions/blog posts etc about this on the internet so
what does this mean?
and how do I fix it?
happy to answer any further questions to help resolve this issue :)
Thanks :)
Extra information*
This is failing because your Event object in code is trying to store your 'start' and 'end' properties as a Date? but you're retrieving them from Firebase Firestore as a Timestamp.
You'll need to do an intermediate step to unwrap the values and then get the date object.
if let end = data["end"] as? Timestamp {
self.end = end.dateValue()
}
Also, you should really be unwrapping the document.data() safely to avoid using the d! and risking a crash.
if let data = document.data(){
// extract values
}
Finally, it may be worth reading the document on sending / retrieving custom objects with Firestore. It makes everything a lot easier.
Firestore.firestore().collection("Event").document(id).getDocument { (document, error) in
if error != nil {
print(error?.localizedDescription as Any)
}
guard let document = document else {
// failed to unwrap document
return
}
if document.exists {
let result = Result {
try document.data(as: Event.self)
}
switch result {
case .success(let event):
if let event = event {
// do something with Event object
} else {
// failed to unwrap Event
}
case .failure:
// failed to form Event
}
} else {
// document doesn't exist
}
}

How to force firebase functions to execute before continuing swift

I am trying to query data from firebase inside a for loop, my problem is since the queries take time to connect, swift is jumping over the queries and coming back later to do them. This creates the problem where my loop counter is ticking up but the queries are being saved for later, when the queries finally do get executed, the counter variable is all out of wack.
Where the code is being skipped is right after the query, where I am trying to append to an array.
func getSelectedData() {
var exerciseIndex = 0
for i in 0...Master.exercises.count - 1 {
if Master.exercises[i].name == self.exerciseName {
exerciseIndex = i
}
}
let numOfSets = Master.exercises[exerciseIndex].totalSets
// For each date record
for count in 0...self.returnedExercises.count-1 {
// Creates a new dataSet
dataSet.append(dataSetStruct())
dataSet[count].date = returnedExercises[count]
for number in 0...(numOfSets - 1) {
// Retrives the reps
let repsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("reps")
repsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
}
else {
// error
}
}
//Retrives the weights
let weightsDbCallHistory = db.collection("users").document("\(userId)").collection("ExerciseData").document("AllExercises").collection(exerciseName).document(returnedExercises[count]).collection("Set\(number + 1)").document("weights")
weightsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].weightsArray.append(data["Weight\(number + 1)"] as! Float)
self.updateGraph()
}
else {
// error
}
}
}
}
}
I even tried breaking out the query into another function but this doesn't seem to fix the issue.
Any help is appreciated, thanks.
EDIT:
func getSelectedData() {
if returnedExercises.count > 0 {
// Create a dispatch group
let group = DispatchGroup()
print("Getting Data")
// For each date record
for count in 0...self.returnedExercises.count-1 {
// Creates a new dataSet
self.dataSet.append(dataSetStruct())
self.dataSet[count].date = self.returnedExercises[count]
for number in 0...(self.numOfSets - 1) {
print("At record \(count), set \(number)")
// Enter the group
group.enter()
// Start the dispatch
DispatchQueue.global().async {
// Retrives the reps
let repsDbCallHistory = self.db.collection("users").document("\(self.userId)").collection("ExerciseData").document("AllExercises").collection(self.exerciseName).document(self.returnedExercises[count]).collection("Set\(number + 1)").document("reps")
repsDbCallHistory.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.dataSet[count].repsArray.append(data["Reps\(number + 1)"] as! Int)
print("Getting data: \(number)")
group.leave()
}
else {
// error
}
}
}
group.wait()
print("Finished getting data")
}
}
I tried to simplify the function for now and only have one database call in the function to try the dispatch groups. I am not sure why firebase is doing this but the code never executes the group.leave, the program just sits idle. If I am doing something wrong please let me know, thanks.
This is what the print statements are showing:
Getting Data
At record 0, set 0
At record 0, set 1
At record 0, set 2
At record 1, set 0
At record 1, set 1
At record 1, set 2
print("Getting data: (number)") is never being executed for some reason.
I am thinking that maybe firebase calls are ran on a separate thread or something, which would made them pause execution as well, but that's just my theory
EDIT2::
func getOneRepMax(completion: #escaping (_ message: String) -> Void) {
if returnedOneRepMax.count > 0 {
print("Getting Data")
// For each date record
for count in 0...self.returnedOneRepMax.count-1 {
// Creates a new dataSet
oneRPDataSet.append(oneRepMaxStruct())
oneRPDataSet[count].date = returnedOneRepMax[count]
// Retrives the reps
let oneRepMax = db.collection("users").document("\(userId)").collection("UserInputData").document("OneRepMax").collection(exerciseName).document(returnedOneRepMax[count])
oneRepMax.getDocument { (document, error) in
if let document = document, document.exists {
// For every document (Set) in the database, copy the values and add them to the array
let data:[String:Any] = document.data()!
self.oneRPDataSet[count].weight = Float(data["Weight"] as! String)!
print("Getting data: \(count)")
completion("DONE")
self.updateGraph()
}
else {
// error
}
}
}
}
}
I tried using completion handlers for a different function and it is also not working properly.
self.getOneRepMax(completion: { message in
print(message)
})
print("Finished getting data")
The order that the print statements should go:
Getting Data
Getting data: 0
Done
Getting data: 1
Done
Finished getting data
The order that the print statements are coming out right now:
Getting Data
Finished getting data
Getting data: 1
Done
Getting data: 0
Done
I am not even sure how it is possible that the count is backwards since my for loop counts up, what mistake am I making?
I think what you need are Dispatch Groups.
let dispatchGroup1 = DispatchGroup()
let dispatchGroup2 = DispatchGroup()
dispatchGroup1.enter()
firebaseRequest1() { (_, _) in
doThings()
dispatchGroup1.leave()
}
dispatchGroup2.enter()
dispatchGroup1.notify(queue: .main) {
firebaseRequest2() { (_, _ ) in
doThings()
dispatchGroup2.leave()
}
dispatchGroup2.notify(queue: .main) {
completionHandler()
}

observeNext called more than once after insert element to MutableObservableArray in Bond Swift Framework

I have a MutableObservableArray object which has a binding with observeNext function of Bond framework. At the first opening of the app, I fetch array from user defaults and insert it to this empty array.
My problem is that after I insert element to array, observeNext function called three times not once. What can be the problem?
var list = MutableObservableArray<Task>([])
_ = self.list.observeNext(with: { element in
if element.diff.count != 0 {
if element.diff.deletes.count >= 1 && element.collection.count == 0 {
self.restoreUserDefaults(with: false)
} else {
self.saveListToUserDefaults(list: element.collection)
}
}
})
Insert Function:
if let savedTask = userDef.object(forKey: self.userDefaultsKeyForList) as? Data {
let decoder = JSONDecoder()
if let loadedList = try? decoder.decode([Task].self, from: savedTask) {
self.list.batchUpdate({ (a) in
a.insert(contentsOf: loadedList, at: 0)
})
}
}
EDIT: I think when It first initialize array with [] It also gets into observeNext. Is it normal?

Retrieving Firebase Database String and converting String to Integer for label not working

Saving data to Firebase and retrieving data to display in label is working but when I try to add an Int to the label it overwrites the label data.
I add to the label with
var pointsCount = 0
func addPoints(_ points: NSInteger) {
pointsCount += points
pointsLabel.text = "\(self.pointsCount)"
}
Then I save the label contents to Firebase as a string.
func saveToFirebase() {
let userID = Auth.auth().currentUser!.uid
let points: String = ("\(self.pointsCount)")
let savedScores = ["points": points,
"blah": blah,
"blahblah": blahblah]
Database.database().reference().child("users").child(userID).updateChildValues(savedScores, withCompletionBlock:
{
(error, ref) in
if let error = error
{
print(error.localizedDescription)
return
}
else
{
print("Data saved successfully!")
}
})
}
I retrieve the string from the Realtime Database and convert it to an Int.
func retrieveFromFirebase() {
guard let userID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(userID).child("points").observeSingleEvent(of: .value) {
(snapshot)
in
guard let points = snapshot.value as? String else { return }
let pointsString = points
if let pointsInt = NumberFormatter().number(from: pointsString) {
let retrievedPoints = pointsInt.intValue
self.pointsLabel.text = "\(retrievedPoints)"
} else {
print("NOT WORKING")
}
}
The label displays the retrieved data from the database perfectly.
Then if I try to add more points to the label, it erases the retrieved data and starts adding from 0 as if the label displayed nil.
I've been searching for answers all day for what seems to be a rather simple problem but haven't been able to figure it out due to my lack of experience.
I have tried separating everything and saving the data as an integer and retrieving the data back as an integer but the issue seems to be from the addPoints function.
Please let me know what I'm doing wrong.
The solution ended up being as simple as adding an 'if' statement to the points function.
Instead of...
func addPoints(_ points: NSInteger) {
pointsCount += points
pointsLabel.text = "\(self.pointsCount)"
}
It needed to be...
func addPoints(_ points: NSInteger) {
if let text = self.pointsLabel.text, var pointsCount = Int(text)
{
pointsCount += points
pointsLabel.text = "\(pointsCount)"
}
}

Swift 3: What's the safest way to unwrap optional values coming from an array?

First, I initialize the variables to hold the stock data
var applePrice: String?
var googlePrice: String?
var twitterPrice: String?
var teslaPrice: String?
var samsungPrice: String?
var stockPrices = [String]()
I fetch current stock prices from YQL, and put those values into an array
func stockFetcher() {
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
if let applePrice = json["query"]["results"]["quote"][0]["Ask"].string {
print(applePrice)
self.applePrice = applePrice
self.tableView.reloadData()
}
if let googlePrice = json["query"]["results"]["quote"][1]["Ask"].string {
print(googlePrice)
self.googlePrice = googlePrice
self.tableView.reloadData()
}
if let twitterPrice = json["query"]["results"]["quote"][2]["Ask"].string {
print(twitterPrice)
self.twitterPrice = twitterPrice
self.tableView.reloadData()
}
if let teslaPrice = json["query"]["results"]["quote"][3]["Ask"].string {
print(teslaPrice)
self.teslaPrice = teslaPrice
self.tableView.reloadData()
}
if let samsungPrice = json["query"]["results"]["quote"][4]["Ask"].string {
print(samsungPrice)
self.samsungPrice = samsungPrice
self.tableView.reloadData()
}
let stockPrices = ["\(self.applePrice)", "\(self.googlePrice)", "\(self.twitterPrice)", "\(self.teslaPrice)", "\(self.samsungPrice)"]
self.stockPrices = stockPrices
print(json)
}
}
}
in cellForRowAt indexPath function I print to the label
if self.stockPrices.count > indexPath.row + 1 {
cell.detailTextLabel?.text = "Current Stock Price: \(self.stockPrices[indexPath.row])" ?? "Fetching stock prices..."
} else {
cell.detailTextLabel?.text = "No data found"
}
I'm running into the issue of printing Current Stock Price: Optional("stock price"), with the word optional. I gather that this is because I'm giving it an array of optional values, but I sort of have to since I actually don't know if there will be data coming from YQL, one of the 5 stocks might be nil while the others have data. From reading other similar questions I can see that the solution would be to unwrap the value with !, but I'm not so sure how to implement that solution when it's an array with data that might be nil, and not just an Int or something.
How can I safely unwrap here and get rid of the word Optional?
First off:
Any time you repeat the same block of code multiple times and only increase a value from 0 to some max, it is a code smell. You should think about a different way to handle it.
You should use an array to do this processing.
How about a set of enums for indexes:
enum companyIndexes: Int {
case apple
case google
case twitter
case tesla
//etc...
}
Now you can run through your array with a loop and install your values more cleanly:
var stockPrices = [String?]()
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
let pricesArray = json["query"]["results"]["quote"]
for aPriceEntry in pricesArray {
let priceString = aPriceEntry["ask"].string
stockPrices.append(priceString)
}
}
}
And to fetch a price from the array:
let applePrice = stockPrices[companyIndexes.apple.rawValue]
That will result in an optional.
You could use the nil coalescing operator (??) to replace a nil value with a string like "No price available.":
let applePrice = stockPrices[companyIndexes.apple.rawValue] ?? "No price available"
or as shown in the other answer:
if let applePrice = stockPrices[companyIndexes.apple.rawValue] {
//we got a valid price
} else
//We don't have a price for that entry
}
I'm writing this outside of Xcode (so there might be typos), but this kind of logic should work.
if self.stockPrices.count > indexPath.row + 1 {
var txt = "Fetching stock prices..."
if let price = self.stockPrices[indexPath.row] {
txt = price
}
cell.detailTextLabel?.text = txt
} else {
cell.detailTextLabel?.text = "No data found"
}
For safe unwrap use that code:
if let currentStockPrice = self.stockPrices[indexPath.row]
{
// currentStockPrice available there
}
// currentStockPrice unavailable
If you need to unwrap multiple variables in one if after another it may lead to unreadable code. In such case use this pattern
guard let currentStockPrice = self.stockPrices[indexPath.row]
else
{
// currentStockPrice is unavailable there
// must escape via return, continue, break etc.
}
// currentStockPrice is available

Resources