I am new to firebase.
I want to make an app with data from my Firebase database to show in a tableview.
Example:
Let's say I have made a database that looks like this:
{
"fruits": {
"Apple": {
"color": "Red",
"taste": "Bla bla...",
},
"Banana": {
"color": "Yellow",
"taste": "Bla bla...",
},
"Pear": {
"color": "Green",
"taste": "Bla bla...",
},
}
}
Firebase automatically sort the items in my database by alphabetic order. Now I want to make a tableview with all the fruits listed in alphabetic order. When I tap on a fruit it will push to a new window with the informations about that specific fruit like "color", "taste", etc.
The important thing to keep in mind is that, whenever I'm adding items to my database (like more types of fruit), it has to keep showing in alphabetic order. I HAVE read the documentation, but without any luck.
How do I do this? I know how to make tableviews and create databases. But how do I connect my database to my tableview and make it print out my data from a-z? maybe by index number?
Thank you. :-)
You may want to consider a different structure
fruits
fruit_id_0 <- key created with childByAutoId
name: "Apple"
color: "Red"
taste: "blah blah"
From there you have a ton of options.
One option is to use .childAdded, which will iterate over all of your nodes, one at a time and leave an observer which notifies the app whenever a new child is added. The dataSource array is ordered by name in code.
let fruitsRef = self.ref.child("fruits")
fruitsRef.observe(.childAdded, with: { snapshot in
let fruitDict = snapshot.value as! [String: Any]
let fruitName = fruitDict["name"] as! String
self.fruitArray.append(fruitName)
self.fruitArray.sort() //sorts in place
self.myTableView.reloadData()
})
From there, any time a new fruit is added the closure will be called. The newly added fruit is added, array sorted and UI updated.
If you have just a few entries the above is fine, but if you have a lot of fruit like I do, it would sort and update the tableView after each fruit is loaded, over and over which could cause flicker or other undesired results.
A better option would be to leverage the fact that .value observers occur after .child added observers. With that in mind, you can load up your entire initial dataset, and once that's complete, sort it and update the tableView. At the same time leaving an observer for new fruit which will add it to the dataSource, sort and update your UI.
Here's some code that would be initated with a button click
func button0() {
self.configureChildAdded()
self.setInitialLoadCompleted()
}
two class vars to keep track of where we are in the loading process and an array to store the fruit names
var initialLoad = true
var fruitArray = [String]()
and then two classes that will initially load and sort your fruits and leave a .childAdded observer which will watch for additional fruits and add them to the dataSource, sort, and update a tableView
func configChildAdded() {
let fruitsRef = self.ref.child("fruits")
fruitsRef.observe(.childAdded, with: { snapshot in //iterate over all fruits
let fruitDict = snapshot.value as! [String: Any]
let fruitName = fruitDict["name"] as! String
self.fruitArray.append(fruitName)
//upon first load, don't reload the tableView until all children are loaded
if ( self.initialLoad == false ) {
self.fruitArray.sort()
self.myTableView.reloadData()
}
})
}
//this .value event fires AFTER the child added events to load the tableView the first time
// all this does is update the UI and set the initalLoad to true and only fires once
func setInitialLoadCompleted() {
let fruitsRef = self.ref.child("fruits")
fruitsRef.observeSingleEvent(of: .value, with: { snapshot in
self.myTableView.reloadData()
self.initialLoad = false
})
}
Note that using this method will totally avoid using any kind of Firebase ordering as it's done in code. This would make it much easier to do multi field ordering etc.
Related
I have a database in Realm which i display in a UITableView which works perfectly. I added a delete functionality which lokks like below.
if let item = items?[indexPath.row] {
do {
try realm.write {
realm.delete(item)
}
} catch {
print("Error deleting item, \(error)")
}
tableView.reloadData()
}
Let's say I have some data in the database like 1,2,3,4,5. When I delete the '2' I want it to be like 1,3,4,5 but it is 1,5,3,4. So the last entry respectively the entry which is displayed at the bottom will always replace the deleted data. How can I fix it?
Objects is Realm are stored as unordered if you want them in order then you have to apply below code or store it as a List.
let result = realm.objects(ModelClass).sorted("attribute", ascending: true)
ModelClass is the name of your Realm Object Class
Attribute is
Object property on the base of which you want them.
Hope it will help you.
I am currently trying out Realm on a test project and I have been struggling with removing a specific object from a List. LensDBObject and ListDBObject. LensDBObject contains a list of lenses and ListDBObject are lists of existing lenses. A lens can be in multiple lists and I'd like to remove a specific lens from a specific list but not remove if from the other lists.
Below are my two classes:
#objcMembers class LensDBObject: Object {
dynamic var id = UUID().uuidString
dynamic var manufacturer = ""
dynamic var series = ""
dynamic var serial = ""
dynamic var isSelected = false
override static func primaryKey() -> String? {
return "id"
}
let objects = LinkingObjects(fromType: ListDBObject.self, property: "lensList")
}
#objcMembers class ListDBObject: Object {
dynamic var listName = ""
let lensList = List<LensDBObject>()
}
Below is my code to find a specific lens in the list I want. The values returned are what I expect.
let listToSearch = realm.objects(ListDBObject.self).filter("listName == %#", "List 542")
print(listToSearch)
let filteredResults = listToSearch[0].lensList.filter("manufacturer == %# AND series == %# AND serial == %#", "Panavision" , "Primo Prime", "407")
print(filteredResults)
However, when I try to delete filteredResults, it deletes it from the lensDBOject altogether. I just want to be able to delete this specific lens from this specific list.
try! realm.write {
realm.delete(filteredResults)
}
I tried using for loops to get the index of the lens in the list and then delete it directly from that. But it still deletes the lens everywhere.
Am I missing something? Should I be using a one-to-many relationship as opposed to a LinkingObject?
Thanks for you help!
Try something like this. You only want to remove the lens from the list, not delete it from the Realm.
try! realm.write {
filteredResults.forEach { lens in
if let index = listToSearch[0].lensList.index(of: lens) {
listToSearch[0].lensList.remove(at: index)
}
}
}
Note that this will remove from that one specific list all lenses that match your filter.
Edit: Updated to reflect Realm's custom List class.
The if let is required, because index(of:) could potentially return nil if the object is not found in the list. Additionally, we must do it one item at a time rather than getting all the indexes first, since removing an item would cause the index array to be wrong.
I have an NSSet which holds a bunch of Detail objects in it. Each Detail object has .name and .text properties. I would like to find one particular Detail in the set where .name = "memo" and see what's being held in its .text property.
How on earth do I do this?
(.indexOf won't work. I don't now if I need to use find and .map somehow, or if there's a means of direct accessing that one particular object in Swift.)
For clarity, I'm using Core Data and each of my "Event" objects has a relationship to many Detail objects. The NSSet of Details is the set that I'm getting off a given Event.
Again, here's what it looks like:
Detail object has properties
.name
.text
I get the details from the active "event" like this:
var someDetails: NSSet()?
someDetails = event.details!
So now I have the set of details for that event.
Now I want to access the detail object whose .name = "memo" and see what it's .text property is.
Creating the details themselves was much easier as all I had to do was create the detail managed object, assign its properties, and add it to a set. When the set had all the objects I needed, I just assigned it to the event.details and saved back to Core Data. I never dreamed it would be much harder to access an individual "detail".
I have no idea what to do next for direct access.
Am I stuck looping through the set, looking at each object, and pulling out the one I want?
Thanks!
You should use Set instead of NSSet, however I will answer you question about NSSet.
I imagine you have a class like this
class Detail: NSObject {
let name: String
let text: String
init(name: String, text: String) {
self.name = name
self.text = text
}
}
and an NSSet like this
let details = NSSet(array: [
Detail(name: "a", text: "1"),
Detail(name: "b", text: "2"),
Detail(name: "c", text: "3"),
Detail(name: "d", text: "4")]
)
now you want all the elms where name is "a", then write
let results = details.flatMap { $0 as? Detail }.filter { $0.name == "a" }
and finally print them
for result in results {
print(result.text)
}
I have a query (written in swift):
FIRDatabase.database().reference(withPath: "\(ORDERS_PATH)/\(lId)")
.child("orders")
.observe(.childAdded, with: { firebaseSnapshot in
let orderObject = firebaseSnapshot.value as! [String: AnyObject]
let order = AppState.Order(
title: orderObject["name"] as! String,
subtitle: orderObject["item_variation_name"] as! String,
createdAt: Date(timeIntervalSince1970: TimeInterval(orderObject["created_at"] as! Int / 1000)),
name: "name",
status: AppState.Order.Status.Pending
)
f(order)
})
My database looks like this:
I want it to just listen all NEW incoming orders. However, every time it initially loads it fetched a bunch of existing orders with it, which isn't what I want.
I do have a created_at (an int that represents that time e.g. 1478637444000) on each of the orders, so if there's a solution that can utilize that that works too.
Is there something wrong with my query?
Observers always fire once and read in "all the data".
A .value observer reads in everything in the node at once, where as a .childAdded observer (from the docs)
This event is triggered once for each existing child and then again
every time a new child is added to the specified path.
If you think about your question, what you are actually looking for is a sub-set of all of your data. This is achieved via a query.
Your query will be to observe all .childAdded events that occur after a certain timestamp. So that's what you need to do!
Craft an observe query leveraging queryStartingAtValue(the timestamp to start at) which will return all children added after that timestamp.
Swift3 Solution:
Just write multiple observers.
You can retrieve your previous data through the following code:
queryRef?.observeSingleEvent(of: .value, with: { (snapshot) in
//Your code
})
observeSingleEvent of type .value just calls once and retrieves all previous data
and then observe the new data through the following code.
queryRef?.queryLimited(toLast: 1).observe(.childAdded, with: { (snapshot) in
//Your Code
})
queryLimited(toLast: 1) with bserving of type .childAdded is being called every time that a new data is available
I have an array of classes, which looks like this:
var myItems = [myClass]()
class myClass: NSObject {
var a: String?
var b: String?
var c: String?
var d: String?
}
What I want is to save the array called myItems into my database, and have every class inside of a personal section inside the database. Basically, I want every class to look like the one called "Eko" in this image:
To clarify, after "Eko" all the rest of the classes which is inside of the array myItems should be displayed. To achieve what the picture is demonstrating, I used this code:
let data = self.myItems[0]
let currU = FIRAuth.auth()?.currentUser?.uid
let userRef = self.ref.child("users").child(currU!).child(data.a!)
userRef.updateChildValues(["a": data.a!, "b": data.b!, "c": data.c!, "d": data.d!])
Obviously, this will only save the class at index 0 from the array myItems into the Firebase Database, which is displayed in the image above.
My question is thus, how do I save the entire array into the database? With my code I can only save 1 class from the array, and I would like to save all of the items into the database, so that they end up looking the same way that the one class does in the image. You could compare this to populating a tableView, where you need the "indexPath.row" to populate it with all the items instead of only one. I hope that I was clear enough!
You can't save a class into Firebase. But.. A class has a similar structure to a dictionary (properties and values, like key: value pairs etc).
Arrays in Firebase should generally be avoided - they have limited functionality and the individual elements cannot be accessed and for any changes you have to re-write the entire array.
Using a structure where the parent key names are created with childByAutoId is usually preferred.
The easiest solution is to simply add intelligence to the class so it would craft a dictionary and then save itself.
Craft a user class
UserClass
var name = String()
var food = String()
func saveToFirebase() {
let usersRef = myFirebase.child(users)
let dict = ["name": self.myName, "food", self.myFood]
let thisUserRef = usersRef.childByAutoId()
thisUserRef.setValue(dict)
}
}
and and array to store them
var usersArray = [Users]()
populate the array
var aUser = UserClass()
aUser.name = "Leroy"
aUser.food = "Pizza"
usersArray.append(aUser)
var bUser = UserClass()
bUser.name = "Billy"
bUser.food = "Tacos"
usersArray.append(bUser)
and then iterate over the array saving each user
for user in usersArray {
user.saveToFirebase()
}
this will result in
users
-Ykasokokkpoad
name: Leroy
food: Pizza
-YJlaok9sk0sd
name: Billy
food: Tacos
which is very similar to the structure you want. There are many other ways of creating this structure. For example, you could craft the entire dictionary in code and write it all out at one time.
Pardon typo's, I wrote this on the fly.
Firebase has no native support for arrays. If you store an array, it really gets stored as an "object" with integers as the key names.
// we send this
['hello', 'world']
// Firebase stores this
{0: 'hello', 1: 'world'}
Read this post for better understanding.