Im trying to update multiple nodes with multiple auto-id by using for-loop. However it always fail. i cant see anything updated on databse.
Is there any other way to implement it?
let ref = Database.database().reference().child("kardexes").child(newKardex.id)
ref.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if (snapshot.exists()){
for treatment in self.treatments {
self.treatmentId = ref.child("treatments").childByAutoId().key
var treatmentData = FirebaseDataType()
treatmentData["name"] = treatment.name
ref.child("treatments").child(self.treatmentId!).setValue(treatmentData){ (error, ref) in
if let error = error {
Log.debug(" >>> error \(error.localizedDescription)")
}
}
}
} else{
print("snapshot doesnt exist")
}
})
I expect to get the following result, but nothing get updated
kardexes
|
-LkcSD2KJLwbCj8KAdsd
|-treatments
|-"-Lkc5MFEGsfbCj8KAmbo"
|-name:"abc"
|-"-Lk5sKCKJLwbCj8KAofj"
|-name:"def"
|-"-Lk5sKFOELwbCj8KAjgu"
|-name:"ijk"
This question is a bit unclear but I think you're asking how to create the 'kardexes' node so it looks like the structure presented in you question.
If so, here's the code that does that
func createKardexesNode() {
let treatmentArray = ["abc", "def", "ghi"]
let ref = self.ref.child("kardexes").child("-LkcSD2KJLwbCj8KAdsd").child("treatments")
for treatment in treatmentArray {
let childRefToAdd = ref.childByAutoId()
childRefToAdd.child("name").setValue(treatment)
}
}
running this code generates a node in Firebase that looks like this
{
"kardexes" : {
"-LkcSD2KJLwbCj8KAdsd" : {
"treatments" : {
"-Ll2dsJivnsH9QRM6MfV" : {
"name" : "abc"
},
"-Ll2dsJivnsH9QRM6MfW" : {
"name" : "def"
},
"-Ll2dsJivnsH9QRM6MfX" : {
"name" : "ghi"
}
}
}
}
}
note that self.ref is a class var that points to the root ref of my Firebase. Substitute your own.
Related
I've spent days researching this including various answers like: Firebase Firestore: Append/Remove items from document array
but can't work out how to actually get this working.
I have two structs:
struct TestList : Codable {
var title : String
var color: String
var number: Int
}
struct TestGroup: Codable {
var items: [TestList]
}
I am able to add data using FieldValue.arrayUnion:
#objc func addNewItem() {
let testList = TestList(title: "Testing", color: "blue", number: Int.random(in: 1..<999))
let docRef = FirestoreReferenceManager.simTest.document("abc")
docRef.updateData([
"items" : FieldValue.arrayUnion([["title":testList.title,
"color":testList.color,
"number":testList.number]])
])
}
The above works as reflected in the Firestore dashboard:
But if I try and remove one of the items in the array, it just doesn't work.
#objc func removeItem() {
let docRef = FirestoreReferenceManager.simTest.document("abc")
docRef.getDocument { (document, error) in
do {
let retrievedTestGroup = try document?.data(as: TestGroup.self)
let retrievedTestItem = retrievedTestGroup?.items[1]
guard let itemToRemove = retrievedTestItem else { return }
docRef.updateData([
"items" : FieldValue.arrayUnion([["title" : itemToRemove.title,
"color" : itemToRemove.color,
"number" : itemToRemove.number]])
]) { error in
if let error = error {
print("error: \(error)")
} else {
print("successfully deleted")
}
}
} catch {
}
}
}
I have printed the itemToRemove to the log to check that it is correct and it is. But it just doesn't remove it from Firestore. There is no error returned, yet the "successfully deleted" is logged.
Note the above is test code as I've simplified what I actually need just for testing purposes because I can't get this working.
Any ideas on what I'm doing wrong here?
You have to use arrayRemove to remove items from arrays.
#objc func removeItem() {
let docRef = FirestoreReferenceManager.simTest.document("abc")
docRef.getDocument { (document, error) in
do {
let retrievedTestGroup = try document?.data(as: TestGroup.self)
let retrievedTestItem = retrievedTestGroup?.items[1]
guard let itemToRemove = retrievedTestItem else { return }
docRef.updateData([
"items" : FieldValue.arrayRemove([["title" : itemToRemove.title,
"color" : itemToRemove.color,
"number" : itemToRemove.number]])
]) { error in
if let error = error {
print("error: \(error)")
} else {
print("successfully deleted")
}
}
} catch {
}
}
}
I've encountered situations where this straightforward approach didn't work because the item was a complex object, in which case I first had to query for the item from Firestore and plug that instance into arrayRemove() to remove it.
The reason your approach doesn't have any side effects is because arrays in Firestore are not like arrays in Swift, they are hybrids of arrays and sets. You can initialize an array in Firestore with duplicate items but you cannot append arrays using arrayUnion() with duplicate items. Trying to append a duplicate item using arrayUnion() will silently fail, such as in your case.
I am building a Share Extension in Swift which saves a document to Firestore. So far I have been able to authenticate the correct user via keychain sharing and app groups. I can also get a documentID from a new document reference:
var ref = Firestore.firestore().collection("stuff").document()
print(ref.documentID) //prints the id
But when I try to save something to Firestore, nothing prints in the console, meaning I get neither a failure or success callback from Firebase (see below where I batch the updates). Here is my ShareController.swift file:
class ShareViewController: SLComposeServiceViewController {
var sharedIdentifier = "asdf"
override func viewDidLoad() {
FirebaseApp.configure()
setupKeychainSharing()
}
func setupKeychainSharing() {
do {
try Auth.auth().useUserAccessGroup(sharedIdentifier)
} catch let error as NSError {
}
}
override func isContentValid() -> Bool {
return true
}
override func didSelectPost() {
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
if let contents = content.attachments {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(m4aType) {
attachment.loadItem(forTypeIdentifier: m4aType, options: nil, completionHandler: { (results, error) in
if error == nil {
if let url = results as? URL {
if let audioData = NSData(contentsOf: url) {
let fileName = url.lastPathComponent
if Auth.auth().currentUser != nil {
guard let myId = Auth.auth().currentUser?.uid else { return }
let batch = Firestore.firestore().batch()
let ref = Firestore.firestore().collection("projects").document()
let project: [String: Any] = [
"ownerId": myId,
"type" : "audio",
"extensionUrl" : audioUrl.absoluteString
]
batch.updateData(project, forDocument: ref)
let privateRef = Firestore.firestore().collection("user-private").document(myId)
let privateUpdate: [String: Any] = [
"projects" : FieldValue.arrayUnion([ref.documentID])
]
batch.updateData(privateUpdate, forDocument: privateRef)
batch.commit(completion: { (error) in
if let error = error {
print("error updating database: \(error.localizedDescription)")
} else {
print("Database updated successfully!!!!!")
self.extensionContext!.completeRequest( returningItems: [], completionHandler: nil)
}
})
}
}
}
}
})
}
}
}
}
}
}
It appears you're trying to create additional documents within the projects node and update the user-private node. If so, the code in the question won't work.
UpdateData: Updates fields in the document referred to by document. If
document does not exist, the write batch will fail.
Here's a working batch write function that adds a document to a projects node with a Firestore generated document ID and child fields for extension, ownerId and type as well as a user_private collection with a document with a documentId of id_0.
func batchWrite() {
let batch = self.db.batch()
let ref = self.db.collection("projects").document()
let project: [String: Any] = [
"ownerId": "id_0",
"type" : "audio",
"extensionUrl" : "some url"
]
batch.setData(project, forDocument: ref)
let privateRef = self.db.collection("user-private").document("id_0")
let privateUpdate: [String: Any] = [
"projects" : "some projects"
]
batch.setData(privateUpdate, forDocument: privateRef)
batch.commit(completion: { (error) in
if let error = error {
print("error updating database: \(error.localizedDescription)")
} else {
print("Database updated successfully!!!!!")
}
})
}
Note that self.db is a class var reference to my Firestore. That makes it so you don't have to keep typing Firestore.firestore() and use self.db instead.
Also note that a batch is probably not needed in this case as it doesn't appear there are a significant number of writes occurring at the same time.
If you're not using batch, the .addDocument will add documents to collections.
Here's a function that writes a task to a tasks collection and auto-generates a documentId
func writeTask() {
let collectionRef = self.db.collection("tasks")
let data = [
"task": "some task"]
var ref: DocumentReference? = nil
ref = collectionRef.addDocument(data: data, completion: { err in
if let error = err {
print(error.localizedDescription)
}
print(ref?.documentID)
})
}
It is likely Firebase wasn't configured for your Share extension. Print statements do not work for share extension. Instead you should make use of NSLog statements i.e NSLog("refDocId:\(ref.DocumentId)"). Then in your xcode, navigate to Window > Devices and Simulators > Open Console. To configure Firebase in your Share extension, use FirebaseApp.configure()
After several hours of trying to figure out what's happening without finding an answer to fill my void anywhere, I finally decided to ask here.
While I assume I do have a concurrency issue, I have no clue as to how to solve it...
I have an application trying to pull data from a Firebase Realtime Database with the following content:
{
"products" : {
"accessory" : {
"foo1" : {
"ean" : 8793462789134,
"name" : "Foo 1"
},
"foo2" : {
"ean" : 8793462789135,
"name" : "Foo 2"
}
},
"cpu" : {
"foo3" : {
"ean" : 8793462789115,
"name" : "Foo 3"
}
},
"ios" : {
"foo4" : {
"ean" : 8793462789120,
"name" : "Foo 4"
},
"foo5" : {
"ean" : 8793462789123,
"name" : "Foo 5"
}
}
}
}
I have a data model in Product.swift:
class Product {
var identifier: String
var category: String
var ean: Int
var name: String
init(identifier: String, category: String, ean: Int, name: String) {
self.init(identifier: identifier)
self.category = category
self.ean = ean
self.name = name
}
}
I want to fetch the data in another class called FirebaseFactory.swift. I plan to use to communicate with Firebase:
import Foundation
import FirebaseDatabase
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
}
// 2nd print statement
print("From outside the closure: \(data)")
// Getting the products yet to be implemented...
return products
}
}
For now, I am simply trying to call the getAvailableProducts() -> [Product] function from one of my view controllers:
override func viewWillAppear(_ animated: Bool) {
let products = FirebaseFactory().getAvailableProducts()
}
My Problem now is that the 2nd print is printed prior to the 1st – which also means that retrieving the data from the snapshot and assigning it to data variable does not take place. (I know that the code to create my Product objects is missing, but that part actually is not my issue – concurrency is...)
Any hints – before I pull out any more of my hairs – is highly appreciated!!
You're on the right track with your theory: the behavior you're describing is how asynchronous data works with closures. You've experienced how this causes problems with returning the data you want. It's a very common question. In fact, I wrote a blog on this recently, and I recommend you check it out so you can apply the solution: incorporating closures into your functions. Here's what that looks like in the particular case you've shown:
func getAvailableProducts(completion: #escaping ([Product])-> Void) {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// do whatever you were planning on doing to return your data into products... probably something like
/*
for snap in snapshot.children.allObjects as? [DataSnapshot] {
let product = makeProduct(snap)
products.append(product)
}
*/
completion(products)
}
}
Then in viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
FirebaseFactory().getAvailableProducts(){ productsArray in
// do something with your products
self.products = productsArray
// maybe reload data if you have a tableview
self.tableView.reloadData()
}
}
If I understand your question, you need to return the data after the event occurs, because is an async event
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
// Process here the snapshot and insert the products in the array
return products
}
}
}
I have this function that is supposed to fetch data from a comments node from firebase. I want to implement pagination to not load 100+ comments at once. Everything seems to be working but my code seems to be failing at casting the snapchat.value to a Dictionary
func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
var query = messagesRef?.queryOrderedByKey()
if comments.count > 0 {
let value = comments.last?.commentID
query = query?.queryStarting(atValue: value)
}
query?.queryLimited(toFirst: 2).observe(.childAdded, with: { (snapshot) in
var allObjects = snapshot.children.allObjects as? [DataSnapshot]
allObjects?.forEach({ (snapshot) in
// print out snapshot and it isn't empty
print(snapshot.value) // here it keeps going into the else statement even though snapshot.value clearly exist.
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
})
}) { (err) in
print("Failed to observe comments")
}
}
My question is can anyone take a look at this and maybe see where I went wrong? My code looks fine to me and I can't see what's is wrong.
My tree looks like this
"Comments" : {
"CCDS" : {
"-KrrsXkj6FznzRD0-Xzs" : {
"content" : "Shawn",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FBC868F8F-E9EC-4B9D-A248-DD2187BC140C.PNG?alt=media&token=fb14700c-2b05-4077-b45c-afd3de705801",
"timestamp" : 1.503102381340935E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
}
},
"MIA" : {
"-Krghz9d5_CPjkmdffef" : {
"content" : "22",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FF50F6915-DEAB-4A5B-B1AB-CABC1E349148.PNG?alt=media&token=4eb7c708-ec87-45bf-952d-0bd410faee50",
"timestamp" : 1.502915064803007E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
},
"-KrpoEnNYsmRZ5guORUj" : {
"content" : "23",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FBC868F8F-E9EC-4B9D-A248-DD2187BC140C.PNG?alt=media&token=fb14700c-2b05-4077-b45c-afd3de705801",
"timestamp" : 1.503067700479352E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
}
}
}
Based off my code it bypasses the key and goes straight to the children.
For example if pass in MIA it should go to MIA and grab the key corresponding to each comment "-KrrsXkj6FznzRD0-Xzs" and "-KrpoEnNYsmRZ5guORUj" but it is returning everything under that unique ID instead. Which is a problem
The code in your callback seems to assume that you get called with a collection of comments. To get such a collection you need to observe the .value event. When you observe the .value event, you callback gets invoked with a single snapshot that contains all the nodes matching the query:
query?.queryLimited(toFirst: 2).observe(.value, with: { (snapshot) in
var allObjects = snapshot.children.allObjects as? [DataSnapshot]
allObjects?.forEach({ (snapshot) in
print(snapshot.key)
print(snapshot.value)
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
})
}) { (err) in
print("Failed to observe comments")
}
When you observe .childAdded, your callback instead gets called for every individual node matching the query. That means you need to get rid of a loop in your code:
query?.queryLimited(toFirst: 2).observe(.childAdded, with: { (snapshot) in
print(snapshot.key)
print(snapshot.value)
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
}) { (err) in
print("Failed to observe comments")
}
I am new to Firebase and relatively new to Swift.
I have firebase set up as below. I have users, followers and blocked users. I take care of the followers in the UITableViewCell class.
I am wondering, before I go any further: how does performance get affected by putting observers in observers in queries in queries. (Hope these are the correct terms) . Is below the right way to go about it?(the most efficient way). It works, but also seems to stutter a bit. I appreciate any feedback.
{
"BlockedByUsers" : {
"ba1eb554-9a81-4a74-bfd9-484a32eee13d" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : true
}
},
"Dates" : {
"1457635040" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : true
},
},
"Locations" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : {
".priority" : "u14dkwm41h",
"g" : "u14dkwm41h",
"l" : [ 51.05521018175982, 3.720297470654139 ]
},
},
"Users" : {
"97fee08f-19b2-4eb5-9eab-4b1985c22595" : {
"blockedUsers" : {
"ba1eb554-9a81-4a74-bfd9-484a32eee13d" : true
},
"following" : {
"51879163-8b35-452b-9872-a8cb4c84a6ce" : true,
},
"fullname" : "",
"dates" : 1457635040,
"location" : "",
},
}
}
my Swift code with the multiple queries I'm worried about:
var usersRef: Firebase!
var userFollowingRef: Firebase!
var blockedByUsersRef: Firebase!
var datesRef: Firebase!
var geofireEndRef: Firebase!
var geoFireEnd: GeoFire? {
return GeoFire(firebaseRef: geofireEndRef)
}
var dateRangeStart = Int()
var dateRangeEnd = Int()
override func viewDidLoad(){
super.viewDidLoad()
usersRef = DataService.ds.REF_USERS
userFollowingRef = DataService.ds.REF_CURRENTUSER_FOLLOWING
blockedByUsersRef = DataService.ds.REF_BLOCKED_BY_USERS
datesRef = DataService.ds.REF_DATES
geofireEndRef = DataService.ds.REF_GEOFIREREF_END
}
override func viewWillAppear(animated: Bool){
super.viewWillAppear(animated)
if userdefaultsUid != nil
{
geoFireEnd!.getLocationForKey(userID, withCallback: { (location, error) in
if (error != nil)
{
print("An error occurred getting the location for \(self.userID) : \(error.localizedDescription)")
} else if (location != nil)
{
self.updateUsersWithlocation(location)
} else
{
print("GeoFire does not contain a location for \(self.userID)")
self.updateUsersWithoutLocation()
}
})
}
}
func updateUsersWithlocation(location: CLLocation)
{
var allKeys = [String]()
let locationQuery = self.geoFireEnd!.queryAtLocation(location, withRadius: 100.0)
locationQuery.observeEventType(GFEventType.init(0), withBlock: {(key: String!, location: CLLocation!) in
allKeys.append(key)
self.datesRef.queryOrderedByKey().queryStartingAtValue(String(self.dateRangeStart)).queryEndingAtValue(String(self.dateRangeEnd)).observeEventType(.ChildAdded, withBlock: {
snapshot in
self.users.removeAll(keepCapacity: true)
self.newKeys.removeAll()
self.tableView.reloadData()
for datesKey in snapshot.children
{
self.usersRef.childByAppendingPath(datesKey.key!).observeSingleEventOfType(.Value, withBlock: { snapshot in
if let key = datesKey.key where key != self.userID
{
if allKeys.contains(key!) {
let newuser = FBUser(userKey: key!, dictionary: snapshot.value as! [String : AnyObject])
self.blockedByUsersRef.childByAppendingPath(key).childByAppendingPath(self.userID).observeSingleEventOfType(.Value, withBlock: { (snapshot) -> Void in
if let _ = snapshot.value as? NSNull
{
// we have not blocked this one
self.blockedByUsersRef.childByAppendingPath(self.userID).childByAppendingPath(key).observeSingleEventOfType(.Value, withBlock: { snapshot in
if let _ = snapshot.value as? NSNull
{
// we are not blocked by this one
if self.newKeys.contains(newuser.userKey) {}
else
{
self.users.append(newuser)
self.newKeys.append(newuser.userKey)
}
}
self.tableView.reloadData()
})
}
})
}
}
})
}
})
})
}
In essence users can be at a certain place at a certain date. They put down the date they are going to be there, as explained in code below. that date may overlap with other users that are going to be in that area, in a period ranging of say 7 days before until 21 days after. those users can be followed, blocked. but I’m getting those to display in the tableView. If they put in a different date or place, a different set of users will pop up.
if let userStartDate = beginningDate as? Double
{
let intUserStartDate = Int(userStartDate)
dateRangeStart = intUserStartDate - 604800
dateRangeEnd = intUserStartDate + 1814400
print(dateRangeStart, intUserStartDate, dateRangeEnd)
updateUsers()
}
else
{
updateUsersWithoutDate()
}
This may or may not be an answer or help at all but I want to throw it out there.
Given that you want to really look for two things: locations and times, we need some mechanics to handle it.
The locations are more static; i.e. the bowling ally will always be the bowling ally and the times are dynamic and we need a range. So, given a structure
{
"events" : {
"event_0" : {
"loc_time" : "bowling_5"
},
"event_1" : {
"loc_time" : "tennis_7"
},
"event_2" : {
"loc_time" : "tennis_8"
},
"event_3" : {
"loc_time" : "dinner_9"
}
}
}
This structure handles both criteria. You can easily query for all nodes that have location of tennis at a time of 7. You can also query the range for tennis from start time of 6 and end time of 9, which will return tennis_7 and tennis_8
Here's some ObjC code to do just that
Firebase *ref = [self.myRootRef childByAppendingPath:#"events"];
FQuery *query = [[[ref queryOrderedByChild:#"loc_time"]
queryStartingAtValue:#"tennis_6"] queryEndingAtValue:#"tennis_8"];
[query observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot);
}];
You can extrapolate from this substituting your locations for location and distance or timestamps for the time.
Another modification (and this may be obvious but stating it for clarity) is to use a reference to your locations instead of the actual name (bowling, tennis); i.e.
events
"event_0" : {
"loc_time" : "-JAY8jk12998f_20160311140200" // locationRef_timestamp
},
locations
-JAY8jk12998f : {
"loc_name": "Fernando's Hideaway"
}
Structuring your data in the way to want to get to it can significantly reduce your code (and the complexity of queries within queries etc).
Hope that helps.