Retrieving an array from Firebase [duplicate] - swift

This question already has answers here:
How to retrieve objects from firebase by key value
(2 answers)
Closed 6 years ago.
I have an array in Firebase composed of 1's and 0's (true and false basically), they are all stored as separate key/value pairs. I would like to retrieve them from Firebase and append each value to an array in Swift.
I have tried this (found elsewhere on SO) but it is not working.
let ref = Firebase(url:"https://<<unique>>.firebaseio.com/users/\(self.UUID)/scoreArray")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("snap is null")
} else {
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
self.scoreArray.append(rest.value! as! String)
}
}
})
It doesn't crash, it just doesn't fill the array, even though if I print(rest.value) it will give me the array.
So I guess the question is, how do I convert rest.value into a usable form?
EDIT Firebase structure as requested.
66EC8AC4-<<rest of UUID>>
creation_date: "Jun 10, 2016"
extra_quiz_1
q1: "a"
q10: "b"
<<Continues>>
scoreArray
0: "0"
1: "1"
2: "0"
3: "0"
<<continues>>

Working with Array's in Firebase is challenging and in general there are better options to structure data.
In this use case, it may work so here's how it's done.
Given a structure similar to yours:
quiz_0
quiz_name: The Planets
scores
0: Mercury
1: Venus
2: Earth
here's how you would read in the data
let quizzesRef = self.myRootRef.childByAppendingPath("quizzes")
quizzesRef.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let quiz_name = child.value["quiz_name"] as! String
print(quiz_name)
let scores = child.value["scores"] as! NSArray
print(scores) //scores is an array
let answer0 = scores[0] //just to demonstrate accessing the array
print(answer0)
}
})
and the output
Planets
(
Mercury,
Venus,
Earth
)
Mercury
That being said, don't use arrays. Here's a suggestion that may be a far more flexible. Renumbering questions is a snap, modifying the question or answer is easy as well. The -Asidijaid keys are generated using childByAutoId - helps to disassociate dynamic data from static keys.
quiz_0
quiz_name: The planets
quiz_data
-Asok99idkasdsl
question_number: 0
question: Which planet closet to the Sun?
answer: Mercury
-Yklaosokjdinoisd
question_number: 1
question: Which rocky planet is hotter than Mercury
answer: Venus
-Klkooksow999sdd
question_number: 2
question: What is the third planet from the Sun
answer: Earth

Related

Parse Large Firebase Snapshot

I am downloading a large file structure from Firebase. However when printing the keys and values one of the subfolders is showing as <__NSArrayM> I tried to cast it to [String:Any] but it is crashing. Here is my code:
DATABASE.mainOrder.child("incomplete").observe(DataEventType.value, with: { (snapshot) in
let array = snapshot.value as! [String:[String:Any]]
for (_, value) in array {
for number in value {
if "\(number.key)" == "itemsInOrder" {
let items = number.value as! [String:Any]
//let items = Array(arrayLiteral: number.value)
print("itemsInOrder: \(items)")
} else {
print("\(number.key): \(number.value)")
}
}
}
})
However it is crashing and giving me this error:
Could not cast value of type '__NSArrayM' (0x2037d2608) to 'NSDictionary' (0x2037d21d0).
This is the code that I am trying to get:
itemsInOrder: [<__NSArrayM 0x281c060a0>({
amount = "2";
length = "5";
height = "7";
width = "10";
})]
Below is the Firebase json export
{
"TH000" : {
"docUUIDStatus" : "Sent",
"expectedShip" : "May 12, 2021",
"itemsInOrder" : [ {
"amountOrdered" : "2000 sq ft",
"bevel" : "None",
"floorLength" : "2-8",
"floorWidth" : "4",
"specialOrder" : "None",
"specialOrderLength" : "",
"status" : "completed"
} ],
"orderDate" : "May 11, 2021",
"orderNumber" : "TH000",
"orderNumberLowercased" : "th000",
"orderTime" : "2:30:30 PM",
"orderType" : "Order",
"purchaseOrderNumber" : "TH10051",
"purchaseOrderNumberLowercased" : "th10051",
"status" : "completed"
}
}
What you have in itemsInOrder is an array of dictionaries, so to get the data from it, you either have to loop over number in a nested loop, or extract it with:
let items = number.value as! [[String:Any]]
Since I find the double [[ a bit hard to see at first glance, I actually prefer this equivalent syntax:
let items = number.value as! [Dictionary<String:Any>]
Unrelated to the actual problem: your data structure is a bit of an antipattern in Firebase, as reading any order also now means that you end up loading all items in that order. This becomes wasteful when you want to show a list of order names.
The more idiomatic data structure it so have two top-level nodes:
orders
orderItems
In the first you store the properties for each order, such as orderType and status, under the order ID. In the second node you store the list of order times, also under the order ID.
With that structure, your current query will require two steps:
A query to determine the IDs of the orders with the requested status.
Then an additional load for each order to get the items for that order.
This second step is not nearly as slow as you may think, as Firebase pipelines the requests over a single existing connection.
The answer provided by Frank is spot on. I would like to suggest another approach.
We've found that DataSnapshots are super flexible and easy to work with - keeping data as a DataSnapshot for a long as possible provides easier access, especially when the structure is a bit deep. When casting to array's and dictionaries you end up with this kind of thing
[String: [String: [String: Any]]]
which is really cumbersome to work with and troubleshoot.
So here's the code that will read in the data as specified in the question. Note we never convert anything to a Dictionary and use arrays to guarantee ordering will be maintained from the retrieved DataSnapshots.
func readData() {
let ref = self.ref.child("main_order").child("incomplete") //self.ref points to my firebase
ref.observe(.value, with: { snapshot in //first node will be TH000
let childSnapArray = snapshot.children.allObjects as! [DataSnapshot]
for childSnap in childSnapArray {
print("parent node: \(childSnap.key)")
let status = childSnap.childSnapshot(forPath: "docUUIDStatus").value as? String ?? "No Status"
print(" status: \(status)")
let itemsInOrder = childSnap.childSnapshot(forPath: "itemsInOrder") as DataSnapshot
let itemSnapArray = itemsInOrder.children.allObjects as! [DataSnapshot]
print(" \(itemsInOrder.key)")
for itemChildSnap in itemSnapArray {
let amount = itemChildSnap.childSnapshot(forPath: "amountOrdered").value as? String ?? "No amount"
let length = itemChildSnap.childSnapshot(forPath: "floorLength").value as? String ?? "No length"
let width = itemChildSnap.childSnapshot(forPath: "floorWidth").value as? String ?? "No width"
let status = itemChildSnap.childSnapshot(forPath: "status").value as? String ?? "No status"
print(" ", amount, length, width, status)
}
}
})
}
I dropped in a few print statements along the way to show the code execution sequence. The output to console is this
parent node: TH000
status: Sent
itemsInOrder
2000 sq ft 2-8 4 completed
The one other thing is the array stored within itemsInOrder. It's not clear why that's an array and in general Arrays should be avoided in The Realtime Database if possible. There are usually better ways to structure the data. Arrays cannot be directly altered - if you want to insert or delete an element the entire array must be read in, modified and written back out.
So the above code may need to be slightly modified to handle the itemsInOrder node if there are multiple array elements, for example.

Check and compare messages in Swift and Firebase

How do I compare the responses of two people and calculate the percentage of similarity? For example, in my database below, Kevin answered questionId with "yes" and questionId1 with "no". John answered "yes" and "yes". I would like the output to show 50% given that they have the same answer for 1 and a different answer for the other.
I'm trying this but not sure how to compare other users:
func calculatePercentage(completion: #escaping ([String])->()) {
let postRef = self.databaseRef.child("responses").child("Kevin")
postRef.observeSingleEvent(of: .value, with: { (snapshot) in
var userIdArray = [String]()
for topic in snapshot.children.allObjects as! [DataSnapshot] {
let question1 = topic.childSnapshot(forPath: "questionId").value
let question2 = topic.childSnapshot(forPath: "questionId1").value
userIdArray.append(topic as! String)
}
completion(userIdArray)
})
}
fetch all responses that you want to compare as Dictionary or Arrays or Objects.
For more information and example please see firebase read_data document.
Compare each questions, it's same or not. then count the numberOfSameAnswers.
Like Joakim Danielson wrote, calculation is
percentageOfSameAnswer = (numberOfSameAnswers/numberOfAllQuestions)*100

Accessing Firebase Data inside unique AutoID

This is my first question and I'm still learning Swift/Xcode/Firebase, so I appreciate your patience. I've been stalking StackOverflow and have found a lot of answers to help with various things, but nothing that makes sense for the problem I've been struggling with for 2 days.
I am writing a program that will save a date picked on a previous viewcontroller and a set of user-entered floats from text fields to a Firebase database, and append each data set as a separate entry instead of overwriting the previous data. Using the first block of code below, I've got this problem solved except I can't find a way to do it without using AutoID. This leaves me with a setup like this in Firebase, but with multiple categories and "optionSelected" sections in each category:
program-name
Category 1
optionSelected
L1cggMnqFqaJf1a7UOv
Date: "21-12-2017"
Variable 1 Float: "12345"
Variable 2 Float: "26.51"
L1ciVpLq1yXm5khimQC
Date: "30-12-2017"
Variable 1 Float: "23456"
Variable 2 Float: "35.88"
Code used to save:
func newWithNewVars() {
let myDatabase = Database.database().reference().child("Category 1").child(optionSelected)
let variable1 = textField1.text
let variable2 = textField2.text
let variable1Float = (textField1.text! as NSString).floatValue
let variable2Float = (textField2.text! as NSString).floatValue
let writeArray = ["Date": textPassedOverDate, "Variable 1 Float": variable1Float, "Variable 2 Float": variable2Float]
myDatabase.childByAutoId().setValue(gasArray) {
(error, reference) in
if error != nil {
print(error!)
}
else {
print("Message saved successfully!")
}
}
}
The problem comes with recalling data. Since the AutoID is unique, I can't figure out how to access the data deeper inside for calculations. Specifically, I want to be able to make a new entry, press the save data button, and have it find the most recent entry in the "optionSelected" section so it can do calculations like subtract the older variable 1 from the new variable 1 and such.
Given the above description, layout, and code used above, what code structure would allow me to find the most recent date and access the data inside the AutoID sections for a specific category and "optionSelected"?
Thank you for your help.
The issue you're having is that you're trying to dig deeper but can't as you don't have a hold of that id. You'll want to use the .childAdded in your reference observation when you want to get inside of a list in your JSON tree when you don't have a hold of that id to get inside - this will be called as many times as there are values inside of Category 1 tree:
let reference = Database.database().reference()
reference.child("Category 1").child("optionSelected").observe(.childAdded, with: { (snapshot) in
let uniqueKey = snapshot.key // IF YOU WANT ACCESS TO THAT UNIQUE ID
print(uniqueKey)
guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
let date = dictionary["date"] as? String
let variableOne = dictionary["Variable 1 Float"] as? Float
let variableOne = dictionary["Variable 2 Float"] as? Float
}, withCancel: nil)
You may also want to avoid using spaces in your database keys to avoid any problems in the near future. I'd stick with the common lowercased underscore practice e.g. "category_1" or "variable_2_float"

Query data by value of deep child [duplicate]

This question already has answers here:
Firebase v3 Query by Grandchild
(2 answers)
Closed 6 years ago.
I have a data structure like this:
groups
-KOrPKM2QUzuMnMlHfJu
name: "Testgroup 1"
members
0: 123456789
1: 987654321
-KOrPKM2QUzuMnMFGfXa
name: "Testgroup 2"
members
0: 123456789
The number of members is not defined.
How can I get every group where one of the members is 123456789?
This question is different from this because in the other one the key of the value to check is actually known.
The unique ID is generated when push is used, to retrieve the values under that node, you can use child_added.
firebase.database().ref('groups').on('child_added', snap => {
firebase.database().ref('groups/'+snap.key+'/members').on('child_added', childSnapshot => {
if(childSnapshot.val() == '123456789') {
console.log('123456789 found in ' + snap.val().name);
}
});
});
The above code adds a child_added listener to groups so the moment it finds an existing child under groups or when a new child is added, it will trigger child_added, now snap.key contains what you want, the unique generated ID, to compare the value stored in members, another child_added listener is set on members which returns all the value stored under 0, 1...
Now childSnapshot.val() contains the value you want to compare, a simple IF statement is enough and if a match is found, you just print the group name with snap.val().name;
I created NSObject class and called it FBNSArrayDB, inside it I created the firebase ID's in your case it will be:
var name: NSString?
var members: NSString?
var 0: NSString?
var 1: NSString?
then do this in your UIViewController
var fbarray = [FBNSArrayDB]()//at the top
inside func
let ref = FIRDatabase.database().reference().child(uid!)// I assume you are saving this under uid nod!
ref.child("groups").observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let xy = FBNSArrayDB()
xy.setValuesForKeysWithDictionary(dictionary)
guard let SomeV = xy.0
else {
print ("NA")
return
}
if SomeV == "123456789"
{
self.fbarray.append(xy) //this way you can use the array values outside the Func
print (xy.name)
print (xy.0)
print (xy.1)
print (snapshot.key) // if you want to get the AutoID
}
}
}, withCancelBlock: nil)

How do I 'seed' using GKRandomSource from Swift's Gameplaykit to remember the shuffle between sessions

I'm new to programming and have been learning Swift by doing a number of online courses. In one of the courses we built a basic trivia game and I've been progressively trying to improve it by doing my own coding (best way to learn!).
Recently I came across what's called a Fisher-Yates shuffle and, after much trial and error (and with the help of the stack overflow community) was able to use GKRandomSource from Swift's Gameplaykit to shuffle my trivia questions around so that they were being asked randomly. This was an improvement on the original arc4random code I was using because the shuffle removed questions already asked from the overall pool of questions, thereby ensuring that they did not repeat (at least in iOS9).
This works well within the session, but once the user quits the app and relaunches it, the shuffle starts from scratch. So I was looking at a way to have the app 'remember' the questions already asked between sessions. My research led me to the idea of seeding and I've been trying to get this to work with my GKRandomSource code, but I'm obviously missing something.
Any advice etc would be most welcome - especially since I'm not entirely sure that this 'seeding' approach will achieve my ultimate aim of not repeating questions already asked in previous sessions of the app.
Below are what I believe to be the relevant bits of my revised code.
All questions and potential answer choices are stored in a .json file as such:
{
"id" : "1",
"question": "Earth is a:",
"answers": [
"Planet",
"Meteor",
"Star",
"Asteroid"
],
"difficulty": "1"
}
I use the following code to load the .json file:
func loadAllQuestionsAndAnswers()
{
let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
let jsonData : NSData = NSData(contentsOfFile: path!)!
allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
//println(allEntries)
}
And below is my most recent code for trying to achieve the shuffle of all questions and replicate it in future sessions):
var allEntries : NSArray!
var shuffledQuestions: [AnyObject]!
var nextQuestion = -1
var mySeededQuestions : [AnyObject]
loadAllQuestionsAndAnswers()
if #available(iOS 9.0, *) {
let lcg = GKLinearCongruentialRandomSource(seed: mySeededQuestions)
let shuffledQuestions = lcg.arrayByShufflingObjectsInArray(allEntries)
nextQuestion++
loadQuestion(nextQuestion)
// Fallback on earlier versions
}else{
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
loadQuestionPreiOS9(randomNumber)
}
I know at the very least I have a problem with the above code, but I'm at a loss. I'm also thinking that maybe I'm missing a step in terms of storing the seed?
For the sake of completeness, I use a label to display the question and four images to display the potential answers, using the following code:
func loadQuestion(index : Int)
{
let entry : NSDictionary = shuffledQuestions[index] as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
func loadQuestionPreiOS9(index : Int)
{
let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
Finally, I use the following code to present the user with a 'Next' button after they've answered a question:
#IBAction func PressedButtonNext(sender: UIButton) {
print("button Next pressed")
if #available(iOS 9.0, *) {
nextQuestion++
loadQuestion(nextQuestion)
}else{
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
loadQuestionPreiOS9(randomNumber)
}
I know my coding is probably quite verbose and unnecessary, but up until this latest improvement it's been working fine and I actually understand most of it (I think!)
There are really two questions here: what you're asking about and what you seem to want. They're both worth answering for different reasons, so...
How to seed a GK(Whatever)RandomSource
(All the GKRandomSource subclasses have seeds, even though the superclass GKRandomSource itself doesn't... that's because each class has its own data type for seeds. But the usage is the same.)
The critical bits of the code you've posted don't even compile due to a type mismatch: the seed/init(seed:) value for GKLinearCongruentialRandomSource is an integer, not an array of objects. The documentation for that value spells out what it's for (emphasis added) and how to use it:
Any two random sources initialized with the same seed data will generate the same sequence of random numbers. To replicate the behavior of an existing GKLinearCongruentialRandomSource instance, read that instance’s seed property and then create a new instance by passing the resulting data to the initWithSeed: initializer.
So, if you want to replicate a sequence of random numbers:
Create a random source with the plain initializer.
let source = GKLinearCongruentialRandomSource()
Save off that source's seed value.
let seed = source.seed // -> some UInt64 value
// write seed to user defaults, a file, a web service, whatever.
Use that random source for whatever.
Later, when you launch again and want the same sequence, read in the seed value and create a random source using the seed.
let seed = // read in seed value from wherever you saved it
let source = GKLinearCongruentialRandomSource(seed: seed)
This still doesn't get you what you're actually looking for, though: If source in step 1 produced the sequence 1, 6, 3, 9, 2, 7, source from step 4 will also produce the sequence 1, 6, 3, 9, 2, 7 — the seed doesn't record where you "left off" in a sequence. Or, since you're using it for an array shuffle, it'll produce the same shuffled ordering of the array as the first shuffle, but it doesn't remember what you did with the shuffled array thereafter.
How to use a shuffled ordering across multiple app launches
If you want to shuffle an array, walk through it in order, and then on a later run of your app continue walking through the same shuffled array from where you left off, you need to design around that requirement.
Shuffle on the first launch.
Record something about the ordering produced. (Say, a mapping of indices in the shuffle to indices in the original data.)
When walking through the shuffled array, record how far you've gone through it.
On later runs of the app, use the record of the ordering and the record of progress to decide where you are.
Here's a rough pass at that. (Note that I'm not touching your data model — this is a program design question, and SO is not a coding service. You'll need to think about how to flesh out this design to match your model and its use cases.)
struct Defaults {
static let lastQuestionIndex = "lastQuestionIndex"
static let questionOrder = "questionOrder"
}
let questions: [Question] // array of model objects, always in fixed order
func nextQuestion() -> Question {
let defaults = NSUserDefaults.standardUserDefaults()
if let lastIndex = defaults.integerForKey(Defaults.lastQuestionIndex) {
// we've run before, load the ordering
guard let shuffledOrder = defaults.arrayForKey(Defaults.questionOrder) as? [Int]
else { fatalError("save questionOrder with lastQuestionIndex") }
// advance the saved index so the next call to this function
// will get the next question
if lastIndex + 1 < count {
defaults.setInteger(lastIndex + 1, forKey: Defaults.lastQuestionIndex)
} else {
// ran out of shuffled questions, forget the order so we
// can reshuffle on the next call
defaults.removeObjectForKey(Defaults.questionOrder)
defaults.removeObjectForKey(Defaults.lastQuestionIndex)
}
// map lastQuestionIndex from sequential to shuffled
// and return the corresponding answer
let shuffledIndex = shuffledOrder[lastIndex]
return questions[shuffledIndex]
} else {
// first run, shuffle the question ordering (not the actual questions)
let source = GKRandomSource()
let sequentialOrder = Array(0..<questions.count)
let shuffledOrder = source.arrayByShufflingObjectsInArray(sequentialOrder)
// save the ordering, and the fact that we're asking the first question
defaults.setObject(shuffledOrder, forKey: Defaults.questionOrder)
defaults.setInteger(0, forKey: Defaults.lastQuestionIndex)
// return the first question in the shuffled ordering
let shuffledIndex = shuffledOrder[0]
return questions[shuffledIndex]
}
}
That's probably a bit pseudocode-ish (so you might need to worry about casting arrays to work with NSUserDefaults, etc), but as a general design it should be enough to give you some food for thought.
You can also use the following to drop off a certain amount of values so if you keep a roll count dropping that many on next start is as easy as :
arc4.dropValues(rollCount)