populate array after adding a new child in swift firebase - ios

This is my code to populate my 'Consumables' array (called in my viewDidLoad):
//---------------------- POPULATE CONSUMABLE ARRAY --------------------------------//
private func populateConsumableArray(){
//let the object populate itself.
self.ref?.child("Consumables").observe(.childAdded, with: { snapshot in
let dataChange = snapshot.value as? [String:AnyObject]
let aRequest = Consumable(aDict: dataChange!)
self.consumableArray.append(aRequest)
self.consumableTable.reloadData()
})
}
The Consumable object class is shown below
public class Consumable{
private var type:String
private var count:String
private var sku:String
init (aDict: [String: AnyObject]){
self.type = aDict["Type"] as! String
self.count = aDict["Count"] as! String
self.sku = aDict["SKU"] as! String
}
The data populates my table view just fine... Below is a picture of the code working...
As you can see by the two images above, the code loads the data just fine... The array is populated just fine as well, not shown because it's not directly related to the problem.
Below is a picture of the database structure:
Now when a new consumable is added via my add function the child added only grabs the first attribute added to the consumable item. (also not included, because I'm sure that is working fine, since it populates the firebase database online, which I will show)
The first attribute being 'Type', I switched the order of how things are added to firebase, added 'Count' first and count ended up being the only attribute grabbed. See the image below for what I mean...
Adding a test consumable:
Now you can see that 'Type' is the only attribute being grabbed and stored in the dataChange dictionary, rather than 'Type', 'Count' and 'Sku'.
This is what my firebase database looks like at the above breakpoint so I know that consumables are being added just fine to firebase, it's just a problem with grabbing them when a new child is added to the 'Consumable' parent:
And then, of course, the failure occurs in my Consumable object init function, since 'Count' and 'Sku' are not in the passed dictionary. See below for the fault:
If I close and reload the application, the table view loads with all the data, even the data I previously added that crashed the app. So my question, why does my populateConsumableArray function not grab all the children of the "Consumables" parent?
For the heck of it, I'll add my addConsumableToFirebase() function below, just in case that is the problem for some reason...

Before I start please post code as text not as pictures, so we can actually copy and paste it if needed... (you mostly did but the issue was indeed in the last pic you posted lol)
Now, looking at the Firebase documentation looks like:
setValue(_:) says:
The effect of the write will be visible immediately and the corresponding events will be triggered. Synchronization of the data to the Firebase Database servers will also be started.
Which is why you're seeing it call immediately with only the Type.
Instead try the provided function for updating multiple value at the same time:
func updateChildValues(_ values: [AnyHashable : Any])
Edit:
Removed a part about probably causing a retain cycle since you don't capture self weakly, but as pointed out in the comments, it doesn't apply here.

Related

Best way to store and refrence lots data in Swift for use in a UITableView

This is a bit confusing so I apologise. But:
I am trying to make an app where the user has the ability to add items to a list of individual items that will be displayed back to them(Something along the lines of a todo list app) as a table view.
However, I have hit a roadblock I need to store several different bits of data for each item(Some Strings, Some ints and a date) in the list.
I think that a class(or struct) would be the best way to do this where an instance of the class holds the information need for each item and then the name of that instance is stored in a list so it can be accessed via the indexPath in the table view.
However, I don't know how I am going to make a new instance of the class for every item because the app could have hundreds of individual items.
I'm sorry, this is so confusing and any help would be appreciated! Feel free to ask for more info
Edit: what I am looking for and I'm sure there's a stupidly easy way of doing it but I'm try to work out how to create an instance of a class when the name of the class is stored in a variable. Ecencialy I want the instance of the class to store the item. To be created when the user inputs the item to be added to the table.
Eg. They enter an item. item1 and the other data that goes along with then I want to be able to store that in instance of the item class but I don't know how to make the name of that instance because the name I want which is item 1 is stored in a variable.
Sorry that's so confusing that's the reason I need help
So first: You can't store the Name of a Class in a Variable and use this variable to get a new instance of a class.
What you need is an Array containing all the different Items. This array is unlimited so you can store as many items in it as you like and you don't need to set a name for each of these Instances.
First create an empty array containing all Items as a property:
var items : [Item] = []
Second call the function populateItemArray() in your viewDidLoad():
private func populateItemArray() {
// Populate the items Array (Are you using CoreData, Realm, ...?)
}
Third, use the Item Array to populate the TableView.
REMEMBER: If your only using one section in your tableView, indexPath.row is always equal to the corresponding item in the array.
E.G. items[indexPath.row]
Hope this helps you!
UPDATE:
Look at this example struct Item. As you can see you can also store a date in it:
struct Item {
// First create all the properties for your struct.
var data1: String
var data2: Int
var data3: String
// You can create properties of any type you want, also of type date.
var dateOfCreation : Date
// Then implement all the methods of your custom Struct Item.
func setDate(with date : Date) {
self.dateOfCreation = date
}
func returnDate() -> Date {
return self.dateOfCreation
}
}

Firebase observeEventType .childAdded doesn't work as promised

When I call this observe function from in my viewcontroller, the .childadded immediately returns a object that was already stored instead of has just bin added like .childadded would suspect.
func observe(callback: RiderVC){
let ref = DBProvider.Instance.dbRef.child("rideRequests")
ref.observe(DataEventType.childAdded) { (snapshot: DataSnapshot) in
if let data = snapshot.value as? NSDictionary {
let drive = cabRide(ritID: ritID, bestemming: bestemming,
vanafLocatie: vanaf, taxiID: taxiID, status: status)
print(drive)
callback.alertForARide(title: "Wilt u deze rit krijgen?", message: "Van: \(vanaf), Naar: \(bestemming)", ritID: ritID)
}
}
}
When I try this function with .childchanged, I only get a alert when it is changed like it suppose to do, but when doing .chiladded, it just gets all the requests out of the database and those requests were already there.
When I add a new request, it also gives an alert. So it works, but how can I get rid of the not added and already there requests?
Does anybody know this flaw?
This is working exactly as promised. From the documentation:
Retrieve lists of items or listen for additions to a list of items.
This event is triggered once for each existing child and then again
every time a new child is added to the specified path. The listener is
passed a snapshot containing the new child's data.
That might seem weird at first, but this is generally what most developers want, as it's basically a way of asking for all data from a particular branch in the database, even if new items get added to it in the future.
If you want it to work the way you're describing, where you're only getting new items in the database after your app has started up, you'll need to do a little bit of work yourself. First, you'll want to add timestamps to the objects you're adding to the database. Then you'll want to do some kind of call where you're asking to query your database by those timestamps. It'll probably look something like this:
myDatabaseRef.queryOrdered(byChild: "myTimestamp").queryStarting(atValue: <currentTimestamp>)
Good luck!

reloadData() doesn't update parent TableView

I have a custom TableViewController called LibraryTableViewController.
class LibraryTableViewController: UITableViewController {
contains this data for rendering.
var items: [item] = []
Then, I have another class for Firebase that inherits the LibraryTableViewController as a delegate. (I am not sure if this is the right description.)
class FirebaseDB : LibraryTableViewController {
Then, I have a global instance of FirebaseDB as
let database = FirebaseDB()
I have an observer listening to the changes in the database in the Firebase class, and I was updating
self.items
Then, trying to refresh the LibraryTableViewController using
self.tableView.reloadData()
I might be wrong in understanding my current problem. What I think is that the items array that I am updating isn't the same instance as the tableView that I see on the app. It is updating the FirebaseDB instance database's items which isn't being rendered on the app. How do I make sure that the right instance is being updated?
a lot of code missing
but you can check:
thread must be main, maybe you call from background
check is the items is changed
the numbers for you
check is the tableview not nil
check your delegate maybe its also nil
but better give us more code)

Observing data on Firebase vs. checking an array?

I've got a situation where I've got a tableview being filled with names from Firebase.
When the view loads in, I pull all the necessary names from firebase, load them into an array, and base my tableview off that array.
I have an "add" button that takes whatever's in a text field and adds that name to both firebase and their list.
What I do not want to allow is for people to add a name that they have already added.
I'm pulling the names they've added from Firebase like:
users
209384092834
Names
Bob
Sue
so if the user were to type in Rob, it'd add it under that "names" bit, but if they typed in Bob/Sue it wouldn't allow them to add that again.
The two ways I see of doing this are to check if the name a user is wanting to add is in the array I'm filling on load, or to check against the names that are under their Names child on firebase.
Are there any strong arguments for using one over the other. Is it a "big deal" to run an observer to firebase? I feel like using firebase here is "safer" than checking the array..like what if the users net is so or inturrupts, the array hasn't filled up yet, and they type in a name to add, add it, and everything is just a mess. I don't even know if something like that COULD happen.
Any advice here on which direction to take and why?
Important :- Never use Arrays or Tuples to store in Firebase Database, always prefer Dictionary
Make your DB look something like this :-
{ users : {
209384092834 : {
Names: {
Bob : True,
Sue : True
}
}
}
}
I would suggest you use a third path :-
Check if that name exists by referring to that child node and checking by taking a particular snap of that path instead of the entire list..
rootRef.child("users").child(timeStamp).child("Names").child(textField.text!).observeEventType(.Value, withBlock: {(snapshotRecieved) in
if snapshotRecieved.exists()
{
//Show Alert that user Exists i.e if user is rob/sue in your case
}else{
let ref = rootRef.child("users").child(timeStamp).child("Names")
ref.observeEventType(.Value, withBlock: {(snapshot) in
if let namesDict = snapshot!.value as? NSMutableDictionary{
namesDict.setObject("True",forKey:textField.text!)
ref.setValue(namesDict)
}
})
}
})

Update dictionary with previously deleted items

I have two dictionaries. Both declared in a viewController, both based on a model structure class.
// ItemDictionary
var ItemDictionary = ItemModel()
var JSONDictionary = ItemModel()
JSON data is fed into the JSONDictionary and then this data is passed to ItemDictionary which feeds a table within ViewDidLoad.
self.ItemDictionary = self.JSONDictionary
All good. The table is nicely populated from JSON data. I can also delete items from the table and the ItemDictionary. However, when I try and add items back by referring to the original dictionary (JSONDictionary) the item has gone.
I understand this is expected. If Dictionary1 = Dictionary2, a new dictionary is not actually created. Only an second address. So if you change Dictionary1, Dictionary2 also changes.
A practical example could be setting a price range. You can reduce the range and have less items displayed on the table. But you can't replace previously deleted items if you wanted to increase the price range. I do not want to recall the JSON script each time I edit the range. Advice appreciated.
As confirmed by the OP, ItemModel is a class and not a Dictionary. To fix this you need to make ItemModel a real Dictionary and thus a value type. This is probably the preferred choice but will be more work.
An alternative would be to add an convenience initializer to the ItemModel class that instantiates a new copy of itself and call that instead of setting self.ItemDictionary = self.JSONDictionary.
Something like this.
init(model: ItemDictionary) -> ItemDictionary {
// copy properties
}
Then create the new copy.
self.ItemDictionary = ItemDictionary(self.JSONDictionary)
Later you can reinitialize ItemDictionary with the same call.
Try this code out-
var dictionary1 = ["name": "Magnus"]
var dictionary2 = dictionary1
dictionary2.removeAll()
print("\(dictionary2) \(dictionary1)")
The output is :-
[:] ["name": "Magnus"]
Thus 2 new dictionaries are being created. If you refer to the Swift documentation, You will find that in swift, references are hardly present.
Some other portion of code might be responsible. Hope this helps :-)

Resources