Persisting Firebase Realtime Database creates null value in snapshot - ios

So my application was working fine; I persisted the database locally using:
Database.database().isPersistenceEnabled = true
I didn't change anything but then all of a sudden it started returning a null snapshot. When I comment out that line, everything is working fine again and the label in the interface is displayed correctly, and this value is synced correctly across multiple devices. Here is a code snippet from my viewDidLoad:
import UIKit
import Firebase
import FirebaseDatabase
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var returnedName: UILabel!
#IBOutlet weak var scannedURLTextField: UITextField!
#IBOutlet weak var reentries: UILabel!
#IBOutlet weak var visitors: UILabel!
#IBOutlet weak var ticketType: UILabel!
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var entryOrReentrySwitch: UISwitch!
var qrDecodedURL : String!
var ticketNumber : String!
var eventID : String!
var numberOfAttendees: String!
var urlDictionary = [String : Any]()
var attendeeRef: DatabaseReference!
var ticketRef : DatabaseReference!
var entries : DatabaseReference!
var entriesRef : DatabaseReference!
var reentriesRef : DatabaseReference!
var entriesInt : Int!
var reentriesInt : Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Database.database().isPersistenceEnabled = true
attendeeRef = Database.database().reference(withPath: "attendees")
ticketRef = Database.database().reference(withPath: "tickets")
entriesRef = Database.database().reference(withPath: "entries")
reentriesRef = Database.database().reference(withPath: "reentries")
self.scannedURLTextField .becomeFirstResponder()
scannedURLTextField.inputView=UIView()//create dummy view to supress keyboard
// Incresase size of switch
entryOrReentrySwitch.transform = CGAffineTransform(scaleX: 1.5, y: 1.5);
//monitor for changes in entries
entriesRef.child("entries").observe(.value, with: { (snapshot) in
print(snapshot);
self.visitors.text = "Visitors: \(snapshot.value as! String)"
let tempValue = snapshot.value as! String
self.entriesInt = Int(tempValue)
})
//Monitor for changes in reentries
reentriesRef.child("reentries").observe(.value, with: { (snapshot) in
// print(snapshot);
self.reentries.text = "Reentries: \(snapshot.value as! String)"
})
statusImage.image = UIImage.init(named: "scanQR.png")
}
The error generated is:
Could not cast value of type 'NSNull' (0x1fb2f3270) to 'NSString' (0x1fb2fcab8).
2019-10-12 18:05:54.061687-0700 VIM Tickets[621:97014] Could not cast value of type 'NSNull' (0x1fb2f3270) to 'NSString' (0x1fb2fcab8).
It fails on the self.visitors.text = "Visitors: \(snapshot.value as! String)" in the monitor for changes in entries section. Again, everything is working fine and there is data in the snapshot when I comment out the database persistence line. I even moved it to the AppDelegate, but it has the same result.
I am at a bit of a loss as to why this is happening when persistence was working before. Any suggestions would be greatly appreciated!

When you have disk persistence enabled, the Firebase client immediately fires with the current value that it knows for the node. It sounds like in your case it thinks that value is null, so it fires with that. If you have a connection to the server, it will moments later fire with the correct value that it got from the server.
The normal approach to deal with this situation is to first check whether the snapshot exists, before processing its contents.

Related

Query Data from Firebase and write in UITextfield

I'm trying to pull data from my firebase project and write the field value in a UITextfield. I can't seem to figure out how to query the data needed for the UITextfields from the firebase field values correctly.
Is there anyway to do this? I have only seen videos of people adding firebase documents to a tableview, not any for direct pull of a firebase value to a UITextfield.
I've tried:
Switching the constants to instance the UITextfield Outlets and optionally unwrap as text fields let username = data[USERNAME] as? UITextField ?? "anonymous"
Creating variables of the UITextfields and equal the collections array data. So something kind like... let self.username = Userdata[0] but I keep getting "expected pattern error"
I know these may not be appropriate syntax but this is the first time I've ever coded and attempted to make an app, but I can't find any tutorial to follow for this scenario. So any information greatly appreciated, Thanks.
Heres what I've got so far...
import UIKit
import FirebaseDatabase
import Firebase
class ProfileVC: UIViewController {
#IBOutlet weak var userImage: UIImageView!
#IBOutlet weak var username: UITextField!
#IBOutlet weak var firstname: UITextField!
#IBOutlet weak var userEmail: UITextField!
#IBOutlet weak var lastname: UITextField!
#IBOutlet private weak var bgView: UIView!
//variables
private var Userdatas = [Userdata]()
private var usersCollectionRef: CollectionReference!
override func viewDidLoad() {
super.viewDidLoad()
usersCollectionRef = Firestore.firestore().collection(USERS_REF)
}
override func viewWillAppear(_ animated: Bool){
usersCollectionRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching docs: \(err) ")
} else {
guard let snap = snapshot else{return}
for document in snap.documents {
let data = document.data()
let username = data[USERNAME] as? String ?? "anonymous"
let firstname = data[FIRST_NAME] as? String ?? "Anonymous"
let lastname = data[LAST_NAME] as? String ?? "Anonymous"
let email = data[EMAIL] as? String ?? "Anonymous"
let documentID = document.documentID
let newuserData = Userdata(username: username, email: email, firstname: firstname, lastname: lastname, documentID: documentID)
}
}
}
}

How to update the labels in app as soon as JSON is fetched from server? If should be real time update

I am making a cricket app. I want to update the labels in iOS from URL every time server sends me the JDON file. How should I write the URL code?
import UIKit
import Foundation
struct jsonScore : Decodable {
let comment : String
let venuDetails : String
let valueToDisplay : String
let bowlingTeam : String
let battingTeam : String
let overs : Double
let targetScore : Int
let wickets : Int
let score : Int
let striker : striker
let nonStriker : nonStriker
let strikerBowler : strikerBowler
}
struct striker : Decodable{
let name: String
let runs: String
let balls: String
let fours: String
let sixes: String
}
struct nonStriker : Decodable{
let name: String
let runs: String
let balls: String
let fours: String
let sixes: String
}
struct strikerBowler : Decodable{
let name : String
let overs : String
let maidens : String
var runs : String
let wickets : String
}
class ViewController: UIViewController {
#IBOutlet weak var Comment: UITextView!
#IBOutlet weak var VenuDetails: UILabel!
#IBOutlet weak var ValueToDisplay: UILabel!
#IBOutlet weak var battingTeam: UILabel!
#IBOutlet weak var bowlingTeam: UILabel!
#IBOutlet weak var wickets: UILabel!
#IBOutlet weak var overs: UILabel!
#IBOutlet weak var score: UILabel!
#IBOutlet weak var striker: UILabel!
#IBOutlet weak var strikerRun: UILabel!
#IBOutlet weak var nonStriker: UILabel!
#IBOutlet weak var nonStrikerRun: UILabel!
#IBOutlet weak var strikerBowlerName: UILabel!
#IBOutlet weak var strikerBowlerOver: UILabel!
var timer = Timer()
// start the timer
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(getUpdatedData), userInfo: nil, repeats: true)
}
#objc func getUpdatedData() {
let jsonUrlString = "url from server"
guard let url = URL(string : jsonUrlString ) else
{ return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
//get your updated data here and update it into the UI.
do {
let jsonscore = try JSONDecoder().decode(jsonScore.self, from: data)
self.VenuDetails.text = jsonscore.venuDetails
self.Comment.text = jsonscore.comment
self.bowlingTeam.text = jsonscore.bowlingTeam
self.battingTeam.text = jsonscore.battingTeam
self.ValueToDisplay.text = jsonscore.valueToDisplay
self.wickets.text = String(jsonscore.wickets)
self.overs.text = String(jsonscore.overs)
self.score.text = String(jsonscore.score)
self.striker.text = jsonscore.striker.name + "*"
self.strikerRun.text = jsonscore.striker.runs
self.nonStriker.text = jsonscore.nonStriker.name
self.nonStrikerRun.text = jsonscore.nonStriker.runs
self.strikerBowlerName.text = jsonscore.strikerBowler.name
self.strikerBowlerOver.text = jsonscore.strikerBowler.overs
print(jsonscore)
}catch let jsonErr{
print("Error serializing json:", jsonErr)
}
}.resume()
}
}
How to I write the code so that as soon as JSON is fetched from server it gets updated in the app? I used timer also but the labels in app are not updating after first update. I want to update the app label as soon as server sends the JSON file and the update should be repeatedly.
Please help me writing the code.
As #Rmaddy said your timer use is quiet a bad idea. You can see why in the apple's Energy Efficiency Guide for iOS Apps
Timers prevent the CPU from going to or staying in the idle state,
which increases energy usage and consumes battery power.
That kind of over use will end up draining the device battery.
You should use something more efficient like web sockets(As #the4kman suggested). By using web sockets you will need to change a little bit your approach. You should consider to change your server to notify the app instead of the app being constantly downloading data from the server.
Here are some web sockets libraries that you can use:
Starscream
Socket.io
SwiftWebSocket

How To Fix: "Expression is Ambiguous".

I am trying to create an app that can help you calculate sales tax on an item. Of course the app requires multiplication But I keep encountering the error:
"Type of expression is ambiguous without more context"
Can you help me? I'm new to swift so also try to explain why I am incorrect. This is my code so far:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var Item: UITextField!
#IBOutlet weak var Tax: UITextField!
#IBOutlet weak var Answer: UITextField!
#IBAction func Calculate(_ sender: Any) {
let a = Item.text
let conversionRate = Tax
let b = Int(a!)! * conversionRate
Answer.text = ("\(b)")
}
}
Thanks!!
Your primary issue is your attempt to multiply an Int and a UITextField.
You attempt to create an Int from Item.text but you make no similar attempt to convert Tax.text to a number.
There are also many other issues with your code. You are using the ! operator too much and your app will crash as a result.
And your naming conventions need to be improved. Variables and methods should start with lowercase letters.
Here's your code as it should be written:
class ViewController: UIViewController {
#IBOutlet weak var item: UITextField!
#IBOutlet weak var tax: UITextField!
#IBOutlet weak var answer: UITextField!
#IBAction func calculate(_ sender: Any) {
if let itemStr = item.text, let taxStr = tax.text, let itemVal = Int(itemStr), let taxVal = Int(taxStr) {
let result = itemVal * texVal
answer.text = "\(result)"
} else {
answer.text = "Invalid values"
}
}
}
You're trying to multiply a variable of UITextField type with a variable of Int. Try this:
class ViewController: UIViewController {
#IBOutlet weak var Item: UITextField!
#IBOutlet weak var Tax: UITextField!
#IBOutlet weak var Answer: UITextField!
#IBAction func Calculate(_ sender: Any) {
guard let a = Int(Item.text ?? "0") else { return }
guard let conversionRate = Int(Tax.text ?? "0") else { return }
let b = a * conversionRate
Answer.text = ("\(b)")
}
}

Setting text of label in cell after request in Swift

I'm working on an app in Swift that makes a request to a server pulls back JSON and parses it into a schedule. I have table view controller with 7 cells and a label in each. I am aiming to change the text of each label after the request.
#IBOutlet weak var firstPeriod: UILabel!
#IBOutlet weak var secondPeriod: UILabel!
#IBOutlet weak var thirdPeriod: UILabel!
#IBOutlet weak var fourthPeriod: UILabel!
#IBOutlet weak var fifthPeriod: UILabel!
#IBOutlet weak var sixthPeriod: UILabel!
#IBOutlet weak var seventhPeriod: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let dayUrl = NSURL(string: "http://localhost:3000/day")
func setSchedLayout() {
var firstPeriodClass = String(defaults.dictionaryForKey("todaySchedule")!["first"]!["name"]!)
self.firstPeriod.text = firstPeriodClass
print(firstPeriodClass)
}
let task = NSURLSession.sharedSession().dataTaskWithURL(dayUrl!) {(data, response, error) in
day = NSString(data: data!, encoding: NSUTF8StringEncoding)!.stringByReplacingOccurrencesOfString("\"", withString: "").stringByReplacingOccurrencesOfString(" ", withString: "").stringByReplacingOccurrencesOfString("D", withString: "d")
var todaySchedule = defaults.dictionaryForKey("FullWeekSchedule")![day]
defaults.setObject(todaySchedule, forKey: "todaySchedule")
setSchedLayout()
}
task.resume()
}
This all happens inside my viewDidLoad()
I already have some data in my UserDefaults.
When my setSchedLayout() function calls at the end of my request, it doesn't change the text value for about 2 minutes. Then it gives me an error that says I'm trying to
modify the autolayout engine from a background thread
And when it does change the text it gives it an optional string. What's going on?

ViewController.Type does not have a member names 'answerOne' - SWIFT

I am making a swift app. It is a trivia app for fun and I have run into an issue that I havent seen before. (I am new to swift and ios development errors)
import UIKit
class TriviaViewController: UIViewController {
#IBOutlet var myQuestionCounter: UILabel!
#IBOutlet var myQuestion: UILabel!
#IBOutlet var firstAnswer: UIButton!
#IBOutlet var secondAnswer: UIButton!
#IBOutlet var thirdAnswer: UIButton!
#IBOutlet var fourthAnswer: UIButton!
var timer = NSTimer() //TO-DO
var count = 0
var questionCounter = 0
var firstQuestion = "What was the first planet to be discovered using the telescope, in 1781?"
var answerOne = "Mercury"
var answerTwo = "Uranus"
var answerThree = "Venus"
var answerFour = "Jupiter"
var firstCorrectAnswer = 2
//This next line gives the ERROR
let questionOneArray = [answerOne, answerTwo, answerThree, answerFour, firstCorrectAnswer]
}
When I declare the "questionOneArray", it says "TriviaViewController does not have a member named 'answerOne'"
Please explain to me what this error is and how I can fix it.
Thank you.
Solution 1 : Your code is not inside a method, you have to wrap it inside the method. Check this stackoverflow answer
Solution 2 : Keep your questions and answers inside struct and access them, if you don't want to wrap it inside a function.
struct firstQuestion {
static var question = "What was the first planet to be discovered using the telescope, in 1781?"
static var answerOne = "Mercury"
static var answerTwo = "Uranus"
static var answerThree = "Venus"
static var answerFour = "Jupiter"
static var correctAnswer = "Correct Answer"
}
var questionOneArray = [firstQuestion.answerOne, firstQuestion.answerTwo, firstQuestion.answerThree, firstQuestion.answerFour, firstQuestion.correctAnswer]

Resources