I'm trying to get quotes out of Firebase, and I'm struggling. Granted, I also have no idea what I'm doing. I could use some help!
In Firebase, my quotes are set up like :
root
--> quotes
--> quoteID
--> quoteText, quoteAttribution
I'm trying to pull quotes down from Firebase, add them to a local array (to later put in a dictionary), and then pull a random one to use in the app. I hope to put the quoteText into quoteLabel.text, and the quoteAttribution into authorLabel.text. I found this solution in another StackOverflow issue, but it throws the following error at line 43:
Could not cast value of type 'NSNull' (0x10f549740) to 'NSDictionary' (0x10f549178).
2018-07-21 22:49:50.241473-0400 Wavefully[72475:1126119] Could not cast value of type 'NSNull' (0x10f549740) to 'NSDictionary' (0x10f549178).
Does anyone have any tips for how I might pull quoteText and quoteAttribution out of Firebase to use in my app?
Here's my code:
class ViewController: UIViewController {
class quoteClass {
var uid = ""
var quote = ""
var author = ""
}
#IBOutlet weak var quoteLabel: UILabel!
#IBOutlet weak var authorLabel: UILabel!
var ref: DatabaseReference?
var databaseHandler: DatabaseHandle?
var quotesArray = [quoteClass]()
override func viewDidLoad() {
super.viewDidLoad()
// Set the reference to Firebase
ref = Database.database().reference()
let quotesRef = ref?.child("quotes")
quotesRef?.observeSingleEvent(of: .value, with: { (snapshot) in
for _ in snapshot.children {
let quoteSnap = snapshot
let quoteKey = quoteSnap.key
let thisQuoteRef = quotesRef?.child("quoteID")
thisQuoteRef?.observeSingleEvent(of: .value, with: { (quoteSnap) in
let singlequoteSnap = quoteSnap
let quoteDict = singlequoteSnap.value as! [String:AnyObject]
let quote = quoteDict["quoteText"]
let author = quoteDict["quoteAttribution"]
let aQuote = quoteClass()
aQuote.uid = quoteKey
aQuote.quote = quote as! String
aQuote.author = author as! String
print(aQuote.quote)
print(aQuote.author)
print(aQuote.uid)
self.quotesArray.append(aQuote)
})
}
})
let singleQuote = quotesArray.randomItem()!
print(singleQuote.uid)
print(singleQuote.quote)
print(singleQuote.author)}}
Thanks a ton for helping!
Alternatively you can also use your data by casting it into NSDictionary like below:
let dictionary = snapshot.value as? NSDictionary
let quote = dictionary["quoteText"] as? String ?? ""
Okay, so I was making it way harder than it needed to be. I did this, and it's working:
func grabData() {
ref = Database.database().reference()
ref?.child("quotes").observe(.value, with: {
snapshot in
for snap in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = snap.value as? [String:AnyObject] else {
return
}
let quote = dictionary["quoteText"] as? String
let author = dictionary["quoteAttribution"] as? String
let id = dictionary["quoteID"] as? String
self.quoteLabel.text = quote
self.authorLabel.text = author
print(quote!)
print(author!)
print(id!)
}
})
}
Now, I just have to call grabData() in my viewDidLoad to get a quote. Up next: randomize what quote is shown. Oh, and probably store it in Core Data or Realm for local storage. 🤓
Thanks for stopping by!
Related
I am trying to create a view controller displaying information of the clicked shop from a UITableView on the previous view controller. However, I cannot retrieve data in the new view controller and I don't know how to solve this issue. Here is my database structure. Thank you for the help.
import UIKit
import Firebase
import FirebaseDatabase
class ShopViewController: UIViewController {
var name :String? // This is the name of the cell clicked on the previous viewcontroller
var ref: DatabaseReference!
#IBOutlet weak var shopName: UILabel!
#IBOutlet weak var shopType: UILabel!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
shopName.text = name
let ref = Database.database().reference().child("shops").childByAutoId().child(name!).child("Details")
ref.observe(.childAdded, with: { snapshot in
let snapshotValue = snapshot.value as! NSDictionary
let imageUrlString = snapshotValue["imageURL"] as! String
print(imageUrlString)
let shoptype = snapshotValue["type"] as! String
self.shopType.text = shoptype
})
}
}
The reference that you have is wrong, childByAutoId() is used to generate a unique id in your database.
Also the id that you currently have is the userid, you need to retrieve the userid:
let user = Auth.auth().currentUser
let uid = user.uid
then the location should be:
let ref = Database.database().reference().child("shops").child(uid).child("Eat").child("Details")
So there's a couple of issues here, first off is your data structure.
Something along these lines would be much easier to read from. You shouldn't need the name field as far as I can tell, and is there a requirement to have the details nested?
Reading the data
Next up, you can refactor your data reference code. I'd firstly recommend extracting the logic into its own method rather than having it directly in viewDidLoad:
var ref: DatabaseReference!
override func viewDidLoad() {
shopName.text = name
self.ref = Database.database().reference()
getShopDetails()
}
func getShopDetails() {
guard let shopName = name else { return }
ref.child("shops").child(shopName).observe(.childAdded, with: { snapshot in
guard let shop = snapshot.value as? [String: AnyObject] else { return }
let imageUrlString = shop["imageURL"] as! String
print(imageUrlString)
let shoptype = shop["type"] as! String
self.shopType.text = shoptype
})
}
I've modified to set the database reference value as otherwise this would crash if you tried to use it elsewhere. I've also added a couple of guard statements to minimise the number of explicit unwraps.
I can't comment on your list code, but have you considered doing the call for all of the shops in this, and mapping the result to a data object, which you can then use to display the data in the screen we're detailing here, rather than doing a separate query every-time you open a detail screen.
I already have setup Firebase on Xcode 9.
My question:
I want to retrieve data from Firebase. This works but when I add another text in a group it wont retrieve.
Example
FIREBASE:
lang :
"Deutsch"
"Englisch"
Firebase Image Example because it's difficult to explain:
import UIKit
import FirebaseDatabase
class ViewController: UIViewController {
#IBOutlet weak var outputone: UILabel!
// #IBOutlet weak var outputtwo: UILabel!
var dbReference: DatabaseReference?
var dbHandle: DatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
dbReference = Database.database().reference()
// dbReference?.child("lang").childByAutoId().setValue("Deutsch")
// dbReference?.child("lang").childByAutoId().setValue("Englisch")
// retrieve data
dbHandle = dbReference?.child("lang").observe(.childAdded, with:{ (snapshot) in
let lang:String? = snapshot.value as? String
self.outputone.text = (lang)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I want that "Deutsch" displays in outputone and "Englisch" in outputtwo
Can someone help ?
Right now you retrieve the entire lang node. So the snapshot you get from Firebase contains both "de": "Deutch" and "en": "English". When you now do snapshot.value as? String, it returns nil since the snapshot doesn't have a single, simple string value.
To get the specific values, you first need to tell the snapshot which child property you want: de or en, and then you can get the string value.
dbHandle = dbReference?.child("lang").observe(.value, with:{(snapshot) in
let de:String? = snapshot.childSnapshot(forPath:"de").value as? String
let en:String? = snapshot.childSnapshot(forPath:"en").value as? String
self.outputone.text = (de)
self.outputtwo.text = (en)
})
when retrieving the data from Firebase, it's come as a chunk. This is when the downCasting comes in handy.
dbHandle = dbReference?.child("lang").observe(.childAdded, with:{(snapshot) in
guard let dictionary = snapshot.value as? [String:Any],
let de = dictionary["de"] as? String,
let en = dictionary["en"] as? String else {
"error occurs when down casting.."
return}
self.outputone.text = de
self.outputwo.text = en
})
whenever dealing with a dictionary approach, it's dangerous to hardcode string key every time. it's error-prone. I recommend you create a struct holding each key so you don't need to type string every time needed.
I have problem reading from Firebase in Swift.
Here is my Firebase database:
and here is my code:
var ref: FIRDatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
NSLog("Reading from DB")
ref = FIRDatabase.database().reference()
self.ref?.child("Frais").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? [String: Int]
var frpx1 = (value?["frpx1"]!)!
var frpx10 = (value?["frpx10"]!)!
var frpx11 = (value?["frpx11"]!)!
var frpx12 = (value?["frpx12"]!)!
var frpx13 = (value?["frpx13"]!)!
var frpx14 = (value?["frpx14"]!)!
var frpx15 = (value?["frpx15"]!)!
var frpx16 = (value?["frpx16"]!)!
})
print(frpx1)
print(frpx10)
print(frpx11)
print(frpx12)
print(frpx13)
print(frpx14)
print(frpx15)
print(frpx16)
}
I did not find the problem.
I do not have the data from database in frpx1, ..., frpx16.
Your code have a couple of minor issues:
you are casting the returned value to [String: Int] when you should be using [String: Any] instead since not all values are String based.
you are printing the results outside the completion handler. You need to wait the handler to be called to then read the results (i.e., when the method observeSingleEvent returns Firebase is still processing your request).
Fixing both issues should get you going:
...
self.ref?.child("Frais").observeSingleEvent(of: .value, with: {
(snapshot) in
guard let value = snapshot.value as? [String: Any] else {
print("Snapshot type mismatch: \(snapshot.key)")
return
}
let frpx1 = value["frpx1"]
let frpx10 = value["frpx10"]
let frpx11 = value["frpx11"]
...
print(frpx1)
print(frpx10)
print(frpx11)
...
})
PS. I also improved your coding style a little to help prevent further issues ;)
I am working with the following Firebase Database:
I add new chatIDs with the following code:
DatabaseReference.users(uid: self.uid).reference().child("chatIds/\(chat.uid)").setValue(chat.uid)
I need to add a single child to the individual "chatIDs" that is a random string that I will generate but I haven't worked with Firebase for that long so I am not sure how to do add children this far in. How can I write the code to do this?
Based on your database structure, a possible implementation of you want would be:
let ref = Database.database().reference()
// Generating the chat id
let refChats = ref.child("chats")
let refChat = refChats.childByAutoId()
// Accessing the "chatIds branch" from a user based on
// his id
let currentUserId = self.uid
let refUsers = ref.child("users")
let refUser = refUsers.child(currentUserId)
let refUserChatIds = refUser.child("chatIds")
// Setting the new Chat Id key created before
// on the "chatIds branch"
let chatIdKey = refChat.key
let refUserChatId = refUserChatIds.child(chatIdKey)
refUserChatIds.setValue(chatIdKey)
I think what you're looking for is this
let key = firebaseRef.child("users").child("\(String(describing: uid))").child("chatIds").childByAutoId().key
let timestamp = Int(Date().timeIntervalSince1970)
let child = ["key":key,
"name": name as String,
"date": birth as String,
"created": "\(timestamp)"]
firebaseRef.child("users").child("\(String(describing: uid!))").child("chatIds").child(key).setValue(child)
as example I'm saving the key, name, date, and created, as Childs of chatIds, with the childByAutoId, that generates you a random key, so you can locate it when searching the object.
import UIKit
import Firebase
class ChatListVC: UIViewController {
var ref: FIRDatabaseReference!
var messages: [FIRDataSnapshot]! = []
fileprivate var _refHandle: FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
self.userDetail()
}
func userDetail(){
_refHandle = self.ref.child("users").child("child id").observe(.value, with: { [weak self] (snapshot) -> Void in
guard let strongSelf = self else { return }
guard let dict = snapshot.value as? [String:Any] else { return }
//access data from dict
let MyName = dict["MyName"] as? String ?? ""
})
}
Trying to retrieve users from firebase and it is not possible stating an error that its returning an optional value , the code has been attached below.
I've added the debugger message below as well.
code
var ref = FIRDatabaseReference.init()
#IBOutlet weak var points: UILabel!
#IBOutlet weak var currentUser: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
}
func setupProfile(){
let uid = FIRAuth.auth()?.currentUser?.uid
let ref = FIRDatabase.database().reference().child("users").child(uid!)
ref.observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let username1 = dictionary["username"] as! String
print(username1)
self.username.text = username1//Error is caused on this line EXC_BAD_Instruction
// ( code= EXC_I386_INVOP,subcode = 0x0)
}
})
}
Delegate Proxy automatically. To disable the proxy, set the flag FirebaseAppDelegateProxyEnabled to NO in the Info.plist Snap (fpzfqMTapNel8F90q8bEO450eYg1) {
email = "abcd#test.com";
points = 100;
username = abcd; } fatal error: unexpectedly found nil while unwrapping an Optional value (lldb) }
To answer the question: the code is malformed and open to crashes in the case of nil values within the snapshot. i.e. that would mean, for example, you try to access the key 'username' within the snapshot.value (as a dictionary) but the 'username' key doesn't exist.
Here's some suggested code with two options
let uid = FIRAuth.auth()?.currentUser?.uid
let ref = FIRDatabase.database().reference().child("users").child(uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
//make sure dict is available before proceeding
if let dict = snapshot.value as? [String: AnyObject] {
//this is a test in response to a comment
let email = dictionary["email"] as! String
print(email)
self.user.text = email
///if you are sure the 'username' key will always exist (neve
let username = dictionary["username"] as! String
print(username)
self.user.text = username
//if you are unsure if the 'username' will exist or not, protect your code!
if let username = dict["username"] as? String {
//do something with username
}
}
})
To go along with the comments, understanding optionals is key to working with Swift and protecting your code (and users).
There are other options as well to protect your code but try this first.