How physical it is to change structure in firebase - ios

If I am storing employee data in firebase which has 3 fields and in future if I want add or remove a field then how physical it is in firebase ? and how can I do it?
I want to use Firebase for one of my iOS project(Swift 4) which I am going to start working on soon so I want to clear the above mentioned concern.

While Lance's answer works - here's a simple alternative. Assume you want to remove an age 'field' from Firebase. Given a structrue
employees
employee_0
name: "Steve"
age: "42"
employee_1
name: "Hank"
age: "37"
if you want to remove the age node from all employee nodes, this will do it
let employeesRef = Database.database().reference().child("employees")
employeesRef.observe(.childAdded, with: { snapshot in
let key = snapshot.key
let refToRemove = employeesRef.child(key).child("age")
refToRemove.removeValue()
})
Adding a field (child node) is even simpler as child node values cannot be nil so when you write the data, it's added to the node
employeesRef.child(employee_key).child("favorite_food").setValue("pizza")
will add a favorite food node with a value of pizza to an employee node.
Note that there are no 'fields' in Firebase, only parent and child nodes which are key: value pairs.

Jay’s answer is the better way to accomplish what you are asking but this is an alternative. If your a beginner this might be easier to understand and start with. As you advance use Jay’s code.
Seems like you want to do something like this.
Your database structure in Firebase is:
root
|
#-012345 // eg. employeeId
|-"firstName": "Hello"
|-"lastName": "World"
|-"age": "25"
You want to add or remove a field there are several ways to do it but this way it's easy. All you have to to is keep track of the original values then add those plus the new values to a Dictionary and exclude whatever you don't want to send to Firebase using the .setValue() method :
// class properties
let rootRef: DatabaseReference? = Database.database().reference()
let employedID = "012345"
let firstName = "Hello" // original value you want to keep
let lastName = "World" // original value you want to keep
let age = "25" // original value you want to REMOVE
lat gender = "female" // NEW value you want ADDED
// press the button to send the write the values to Firebase
#IBAction fileprivate func updateEmployeeButton(_ sender: UIButton) {
// add the values to a dictionary
let dict = [String:Any]()
dict.(firstName, forKey: "firstName")
dict.(lastName, forKey: "lastName")
dict.(gender, forKey: "gender")
let employeeIdRef = rootRef?.child(employedID)
employeeIdRef?.setValue(dict) // call Firebase's .setValue method and pass the dict as an argument
}
Your database structure will now look like this:
root
|
#-012345
|-"firstName": "Hello"
|-"lastName": "World"
|-"gender": "female"
Notice the way I’m proposing complete overwrites what was at that node but Jay’s way keeps what was there but still gets you the changes you want to make.

Related

iOS: How to get and shuffle autoID data of users stored in childnode of firebase?

Currently my data is structured like this in firebase:
Image: Firebase structure
I would like to grab a random autoID from the child node of the following tab and and present it as an option in a multiple choice format so that it looks like this (where option A,B,C,D are random but not the same):
Image: How the app should look
I'm new to swift and firebase so was wondering if you can help me with code that I can write to:
Enter the child node that shows who the user is following
Grab 4 random AutoID's from the list and present them as options A,B,C,D
Allowing the options to be UIButtons (or similar) so that I can send this screen to the user who was selected (as a notification)
Presenting the 4 users profile pictures (profileImageUrl) on the UIImageview so you can scroll through them (- this is an extra step but would also appreciate help on it)
I hope that makes sense,
Thanks a lot in advance :)
There are several questions within the question so let's address one.
How to select a random node from Firebase Realtime Database. Well, you don't do it with the Firebase API as Firebase doesn't offer that functionality. However, you can still do it.
First lets start with a typical users node
users
uid_0
name: "Frank"
uid_1
name: "Leroy"
uid_2
name: "Henry"
uid_3
name: "Fred"
So if that's the users node, you can use it for this solution. However, many users node have a lot of other data stored in them - and to select a random node we don't care about that data. What we do is create another node that contains only the node keys - this will keep is pretty small, even if there are thousands of users
user_keys
uid_0: true
uid_1: true
uid_2: true
uid_3: true
Then, to select a random node, we load in all of the user keys using .value, iterate over them to load the keys into an array, then a Swift 4 random number generator to select some keys which we load into another array.
Then we iterate over that array to grab the actual user nodes from Firebase. We also don't want duplicates so as we select nodes from the array, we remove those nodes so they are not selected again. Here's a function to select 3 random nodes, retrieve the users data and print the users name.
func getThreeRandomNodesAndPrintUserName() {
var myKeyArray = [String]()
let ref = self.ref.child("user_keys")
//retreives all nodes in the user_keys node
ref.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
myKeyArray.append(key)
}
var randomKeyArray = [String]()
for _ in 0...2 { //will iterate three times
let count = myKeyArray.count //get the number of elements
let randomInt = Int.random(in: 0..<count) //get a random index for the array
let randomUserKey = myKeyArray[randomInt]
randomKeyArray.append(randomUserKey)
myKeyArray.remove(at: randomInt) //remove that object so it's not selected again
}
let numberOfKeys = randomKeyArray.count
for i in 0..<numberOfKeys {
let thisUserKey = randomKeyArray[i]
let userRef = self.ref.child("users").child(thisUserKey)
userRef.observeSingleEvent(of: .value, with: { snapshot in
let name = snapshot.childSnapshot(forPath: "name").value as! String
print(name)
})
}
})
}
and then just a call to that function will print three random users names
self.getThreeRandomNodesAndPrintUserName()
and the output for first run
Fred
Frank
Henry
and then the second run
Henry
Leroy
Fred
etc

Passing Dictionary to Analytics parameters: Urban Airship

I am trying to implement Urban Airship Analytics in my app. I want to track each and every event in my app, for that I have made a different class and passed tracking data as a dictionary.
Following https://docs.urbanairship.com/platform/ios/?swift#ios-screen-tracking link for the same.
I am passing parameters as:
UAirship.shared().analytics.trackScreen("MainScreen")
let event = UACustomEvent()
event.properties = createParamDictionary(paramDict1,paramDict2)
event.track()
As event properties is readonly, I can not assign/add data to it.
And the only option I can see is adding data one by one according to its defined type.
ie.
event.setStringProperty("abcd", forKey: "abcd")
event.setNumberProperty(123, forKey: "xyz")
Which is very tedious in my case.
So My questions are:
Am I doing it correctly?
If Yes, then is there any other variable or some way from which I can directly add parameters?
I also want to add User_id for tracking particular user. Any provision for this?
Thanks.
I think that you can create a custom method to UACustomEvent class which takes a dictionary and sets values using UA defined method, something like this,
extension UACustomEvent {
func setEventProperties<T: Any>(_ values: [String: T]) {
for keyValuePair in values {
if let value = keyValuePair.value as? String {
setStringProperty(value: value, forKey: keyValuePair.key)
} else if let value = keyValuePair.value as? Int {
setNumberProperty(value: value, forKey: keyValuePair.key)
}
}
}
}
That way, you dont have to use setNumberProperty or setStringProperty each time, you want to set events. You can simply do it like this,
event.setEventProperties(["abcd": "abcd", "xyz": 123])

Firebase overwriting entries

I am new to Firebase and not sure how to best explain this but I will try.
I am trying to have my app create an entry for each user. Then each user entry has multiple (0 through n) sub-entries where each sub-entry is a simple string. Basically there is a user-id (the main entry) and their tasks are the sub-entries.
Now my problem is whenever I push data (the sub-entries) to the main entries, all of the previous sub-entries are deleted and only the most recent one is pushed. I have been looking through the documentation and Googling like crazy but nothing seems to work.
I have tried this:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["test": String(text)]
dataRef.setValue(data)
}
Which appends to the user-id entry fine, with a key of "test" and a value of the 'text'
So then I kill the app and change it to:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["CHANGED": String(text)]
dataRef.setValue(data)
}
And it pushes fine, but then the previous entry was just deleted and the only entry left is this one.
What I am trying to do is maybe incrementally (having a numbered key possibly?) add items one by one without having other entries deleted.
I hope this makes sense :P and any help is greatly appreciated!
What is happening here is, you are setting the entire branch (Users/UserID##), to a value, and that value is a single node Changed:<somestring>
Conceptually, it may help to think of the key you want to set as being just another branch e.g (Users/UserID##/TaskID##)
So conceptually, instead of approaching it like this:
Users/UserID = Key:Value
Approach it like this:
Users/UserID/Key = Value
Note: the branch Users/UserID/Key does not have to exist prior to you assigning it a value.
e.g you could change your reference to point at the subkey you want to add or change:
let dataRef = root.childByAppendingPath(uid as! String + "/Task001")
dataref.setValue(String(text))
I concur that what you are doing is a great way to start learning Firebase, and how it works. But once you get going, instead of generating and using your own key for your list of subtasks, do look into childByAutoId, it will automatically create the subkeys for you, plus much more, and is much easier to manage and code.
Documentation Here
Edit: Suggest referring to Frank's better answer below.
An alternative to #MtlDev's answer would be to use updateChildValues():
let data = ["CHANGED": String(text)]
dataRef.updateChildValues(data)
While setValue() replaces the current data with the new value, updateChildValues() updates it in place.
See the Firebase documentation on updating saved data.

Add value instead of change value In Firebase with Swift

I would like to save and retrieve features to and from Firebase into a TableView.
The child I would like to save them under is the uid (unique user id)
so a feature would look like this in the database:
Firebase database
The ideal situation, is how the "derde" is saved, so the uid as a key and "derde" as the value.
#IBAction func saveButtonPressed(sender: AnyObject) {
let featureContents = addFeatureTextField.text
if featureContents != "" {
// Build the new Feature.
let newFeature: String = featureContents!
let ref = DataService.dataService.FEATURE_REF.childByAppendingPath(uid)
ref.setValue(newFeature)
where uid is a String, retrieved from authdata somewhere else in the code.
If I save it like this, it saves it to the specific uid path. If I want to add another feature by clicking on the + in the TableViewController, it saves it to the same path, so the Firebase database is updated with the new value and so instead of two features you only end up with one updated feature.
You can prevent this by working with the chilByAutoId() method, to save a list of items. The code would look like this:
#IBAction func saveButtonPressed(sender: AnyObject) {
let featureContents = addFeatureTextField.text
if featureContents != "" {
// Build the new Feature.
let newFeature: String = featureContents!
let ref = DataService.dataService.FEATURE_REF.childByAutoId().childByAppendingPath(uid)
ref.setValue(newFeature)
via this way, a feature is saved, as you can see in the above image at: "vierde"
This allows you to save multiple features with all the same uid, but different autoId.
But, if I save it like this, my tableView stays empty. The TableViewController is like this:
DataService.dataService.FEATURE_REF.observeEventType(.Value, withBlock: { snapshot in
// The snapshot is a current look at our features data.
print("The features in the tableView should be \(snapshot.value)")
self.features = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
// Make our features array for the tableView.
if let postDictionary = snap.value as? String {
print("All in")
let key = snap.key
let feature = Feature(key: key, value: postDictionary)
// Items are returned chronologically, but it's more fun with the newest features first.
self.features.insert(feature, atIndex: 0)
}
}
}
// Be sure that the tableView updates when there is new data.
self.tableView.reloadData()
})
}
Problem lies in this code: if let postDictionary = snap.value as? String {
This conditional binding does not succeed, because the value is not a String, but the autoId key has no value, only the child under it which is the uid has a value "vierde"
Two possible solutions which I am asking you guys:
1) How can I save multiple features with the same uid without using the autoId?
2) If I am obliged to use the autoId, how can I make sure it observes the value of the uid key under the autoId, instead of the non existing value of the autoId.
Thanks for your help!
I think the answer to the question is to build a dictionary out of the key:value pairs of data and store that as a child of your uid node
let featureDict = [ "feature_0": "cool feature", "feature_1": "great feature"]
let ref = DataService.dataService.FEATURE_REF.childByAppendingPath(uid)
ref.setValue(featureDict)
results in
the_uid
feature_0: "cool feature"
feature_1: "great feature"
The limitation here is the key's names, and then the ability to add even more data about each feature.
Here's a potentially better option
the_uid
auto_id_0
feature_name: #"cool feature"
summary: "Everything you'd ever want to know about this feature"
auto_id_1
feature_name: #"great feature"
summary: "Info about this great feature"
The auto_id_x is generated by autoId and allows you to add however many features you want, change their names and summaries. etc. The children of each auto_id_x are (or could be) stored in a dictionary and saved per the above example.

Confused on snippet of code for implementing iCloud behavior on iOS

The code is from a book. In terms of overall app architecture (MVC), it's part of the Model. The model has two main components:
An array of tags called tags
A dictionary of tag - query called searches
The app saves these pieces of data in the NSUserDefaults (iOS defaults system) and on iCloud. The following method is called when a change in iCloud is signaled. The parameter is an instance of NSNotification.userInfo
// add, update, or delete searches based on iCloud changes
func performUpdates(userInfo: [NSObject: AnyObject?]) {
// get changed keys NSArray; convert to [String]
let changedKeysObject = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey]
let changedKeys = changedKeysObject as! [String]
// get NSUbiquitousKeyValueStore for updating
let keyValueStore = NSUbiquitousKeyValueStore.defaultStore()
// update searches based on iCloud changes
for key in changedKeys {
if let query = keyValueStore.stringForKey(key) {
saveQuery(query, forTag: key, saveToCloud: false)
} else {
searches.removeValueForKey(key)
tags = tags.filter{$0 != key}
updateUserDefaults(updateTags: true, updateSearches: true)
}
delegate.modelDataChanged() // update the view
}
}
My question is on the if - else inside the for loop. The for loop iterates over keys that where changed; either the user adds a new search, updates an existing search, or deletes a search. But, I don't understand the logic behind the if-else. Some clarifying thoughts would be appreciated. I've read it over and over but it doesn't tick with me.
if let query = keyValueStore.stringForKey(key)
means that if keyValueStore contains a string corresponding to key, then this string will be assigned to the constant query.
This is called "safe unwrapping":
inside the if let ... condition, the query is safely saved with saveQuery because using if let ... guarantees that the value of keyValueStore.stringForKey(key) won't be nil.
If the value is nil, then in the else branch, the filter method is used to update the tags array without the key we just processed: tags.filter{$0 != key} means "return all items in tags that are different from key" (the $0 represents the current item from the array processed by filter).

Resources