How to delete from Firebase database - swift

Hi i have followed this tutorial: https://www.youtube.com/watch?v=XIQsQ2injLo
This explains how to save and retrieve from the database, but not how to delete. I am wondering how to delete the database node that belongs to the cell that is being deleted. Thanks

Edit. Code updated for Swift 3 and Swift 4.
I'm always using the remove with completion handler:
static let ref = Database.database().reference()
static func remove(child: String) {
let ref = self.ref.child(child)
ref.removeValue { error, _ in
print(error)
}
}
So for example if I want to delete the following value:
I call my function: remove(child: "mixcloudLinks")
If I want to go deeper and delete for example "added":
I need to change the function a little bit.
static func remove(parentA: String, parentB: String, child: String) {
self.ref.child("mixcloudLinks").child(parentA).child(parentB).child(child)
ref.removeValue { error, _ in
print(error)
}
}
Called like:
let parentA = "DDD30E1E-8478-AA4E-FF79-1A2371B70700"
let parentB = "-KSCRJGNPZrTYpjpZIRC"
let child = "added"
remove(parentA: parentA, parentB: parentB, child: child)
This would delete just the key/value "added"
EDIT
In case of autoID, you need to save the autoID into your Dictionary to be able to use it later.
This is for example one of my functions:
func store(item: MyClassObject) {
var item = item.toJson()
let ref = self.ref.child("MyParentFolder").childByAutoId()
item["uuid"] = ref.key // here I'm saving the autoID key into the dictionary to be able to delete this item later
ref.setValue(item) { error, _ in
if let error = error {
print(error)
}
}
}
Because then I'm having the autoID as part of my dictionary and am able to delete it later:
Then I can use it like .child(MyClassObject.uuid).remove... which is then the automatic generated id.

we can store the randomly generated id as the user id in the database and retrieve that to delete
for e.g. :
let key = ref?.childByAutoId().key
let post = ["uid": key,
"name": myName,
"task": myTask]
ref?.child(key!).setValue(post)
in order to delete
setvalue of the id as nil
for e.g. :
var key = [string]()
ref?.child(key[indexPath.row]).setValue(nil)
key.remove(at: indexPath.row)
myArray.remove(at: indexPath.row)
myTable.reloadData()

Related

Reading Firestore Document containing an array of references

Thanks in advance for the help. I'm teaching myself Swift and trying to figure out how to retrieve the following data from Firebase. Here's my Firebase Data Model...
Groups (Collection)
-> GroupName (String)
-> Owner (References to someone in the Players collection)
Players (Collection)
-> PlayerFirstName
-> PlayerLastName
The Swift I've written to retrieve this data is in a ViewModel. getAllGroups is called from onAppear in the View and looks like this...
class Group: Identifiable, ObservableObject {
var id: String = UUID().uuidString
var name: String?
var owner: Player?
}
class GroupViewModel: ObservableObject {
#Published var groups = [Group]()
private var db = Firestore.firestore()
func getAllGroups() {
db.collection("groups").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No groups")
return
}
self.groups = documents.map { (queryDocumentSnapshot) -> Group in
var group = Group()
let data = queryDocumentSnapshot.data()
group.name = data["name"] as? String ?? ""
//
// LIKE --- SHOULD THIS CALL TO GETPLAYER use AWAIT, FOR EXAMPLE?
// WE'RE EXECUTING THE CLOSURE FOR THE FIRST CALL AND ABOUT TO MAKE A SECOND
//
group.owner = self.getPlayer(playerRef: data["owner"] as! DocumentReference)
return group
}
}
}
func getPlayer(playerRef: DocumentReference) -> Player {
var player = Player()
playerRef.getDocument { (document, error) in
guard error == nil else {
print ("error", error ?? "")
return
}
if let document = document, document.exists {
let data = document.data()
if let data = data {
player.firstName = data["firstname"] as? String
player.lastName = data["lastname"] as? String
}
}
}
return player
}
}
The sorta obvious problem here is the closure for retrieving the parent Group executes and then goes and tries to retrieve the Owner. But by the time the closure inside getPlayer completes... the Group has already been established.
Groups will have...
group[0]
-> GroupName = "Cool Name Here"
-> Owner = nil
group[0]
-> GroupName = "Different Cool Name"
-> Owner = nil
even though each Group definitely has an Owner.
I get there's some stuff here about asynchronous calls in Swift and how best to handle that... I'm just not sure what the proper pattern is. Thanks again for the help and advice!
-j
To restate the question:
How do you nest Firestore functions
There are 100 ways to do it and, a lot of it depends on the use case. Some people like DispatchGroups, others like escaping completion handlers but in a nutshell, they pretty much do the "same thing" as the following code, written out "long hand" for readability
func populateGroupArray() {
db.collection("groups").addSnapshotListener { (querySnapshot, error) in
guard let docs = querySnapshot?.documents else { return }
for doc in docs {
let groupName = doc.get("name") as! String
let ownerId = doc.get("owner_id") as! String
self.addToArray(groupName: groupName, andOwnerId: ownerId)
}
}
}
func addToArray(groupName: String, andOwnerId: String) {
db.collection("owners").document(andOwnerId).getDocument(completion: { snapshot, error in
let name = snapshot?.get("owner_name") as! String
let group = Group(groupName: groupName, ownerName: name)
self.groups.append(group)
})
}
In summary; calling populateGroupArray reads in all of the documents from the groups collection from Firestore (adding a listener too). We then iterate over the returned documents to get each group name and the owner id of the group.
Within that iteration, the group name and ownerId are passed to another function that reads in that specific owner via it's ownerId and retrieves the name
Finally, a Group object is instantiated with groupName and owner name being populated. That group is then added to a class var groups array.
Now, if you ask a Firebaser about this method, they will generally recommend not reading large amounts of Firebase data 'in a tight loop'. That being said, this will work very well for many use cases.
In the case you've got a HUGE dataset, you may want to consider denormalizing your data by including the owner name in the group. But again, that would be a rare situation.

How to call combine multiple completion handlers to combine data to one new array

I have been stuck for a while now and any advice would be greatly appreciated. I am creating an app that uses Firebase database and I have created 5 classes that hold different data in Firebase. I'm creating a tableview that needs to display information from each of the 5 classes (Profile name, image, then information about a league, and info about scores). So in my new class I created a function calling for data from firebase from each class...
For example: GET all players from X league {
FOR every player in the league {
GET the players information
THEN GET the scores
THEN on and on
once we have all information APPEND to new array
}
and then rank the array
}
After all this runs I want to reload the table view on the VC
SO my solution works on the original load but if I back out and re enter the screen the names and images repeat.
To be exact when the indexes print to the console I get
"Player 1: Zack"
"Player 2: John"
However, the screen shows John's image and name repeatedly. BUT only that class... All other data stays where it is supposed to be. And the original functions are all written the same way.
I'm thinking it's something to do with memory management or I wrote my completion handler poorly?
Here is the code in the new array class:
You'll also notice that my completion() is inside my for in loop which I HATE but it's the only way I could get the function to finish before completing.. Otherwise the function completes before the data is ready.
func getLeaderboard(leagueID: String, completion: #escaping ()->()) {
print("League Count After removeAll \(self.rankedGolfers.count)")
self.leagueMembers.getLeagueMembers(leagueID: leagueID) {
print("HANDLER: Step 1: Get League Members")
for member in self.leagueMembers.leagueMembers {
print("Golfer Member ID: \(member.userID)")
self.golferInfo.getGolferInfo(userKey: member.userID, completion: {
print("HANDLER: Step 2: Get player profile info")
print("Golfer Name3: \(self.golferInfo.golfers[0].firstName) \(self.golferInfo.golfers[0].lastName)")
self.handicapHelper.getRounds(userID: member.userID, completion: {
print("HANDLER: Step 3: Get players Handicap")
print("Golfer Handicap3: \(self.golferInfo.golfers[0].lastName): \(self.handicapHelper.handicap)")
self.leagueInfo.getLeagueInfo(leagueID: leagueID, completion: {
print("HANDLER: Step 4: Get league info")
let golferIndex = self.golferInfo.golfers[0]
let memberInfoIndex = self.leagueInfo.leagueInfo[0]
let golferID = member.userID
let profileImg = golferIndex.profileImage
let golferName = "\(golferIndex.firstName) \(golferIndex.lastName)"
let handicap = self.handicapHelper.handicap
let golferLeaguePardieScore = member.pardieScore
let leagueRoundsPlayed = member.numberOfRounds
let roundsRemaining = memberInfoIndex.leagueMinRounds - leagueRoundsPlayed
let currentWinnings = member.currentWinnings
let newGolfer = Leaderboard(golferID: golferID, profileImg: profileImg ?? "No Img", golferName: golferName, golferHandicap: handicap, golferLeaguePardieScore: golferLeaguePardieScore, roundsPlayedInLeague: leagueRoundsPlayed, roundsRemaining: roundsRemaining, currentWinnings: currentWinnings)
self.rankedGolfers.append(newGolfer)
print("HANDLER: Step 5: Add golfer to array")
//print("Golfer Name 4: \(newGolfer.golferName)")
//print("Rounds Remaining: \(newGolfer.roundsRemaining)")
print("league Member Count: \(self.rankedGolfers.count)")
self.getLeaderboardRanking()
print("HANDLER: Step 6: Rank Array")
//print("COMPLETION: \(self.rankedGolfers.count)")
completion()
})
})
})
}
}
}
Thank you for any help possible!
I think we can solve this with a DispatchGroup, which will ensure all of the data is loaded for each user, then append the user to an array used as a tableView dataSource and then reload the tableView upon completion.
To keep it simple we'll start with a UserInfo class which stores their uid, name, favorite food and handicap.
class UserInfoClass {
var uid = ""
var name = ""
var favFood = ""
var handicap = 0
}
and a class var array used as the dataSource for the tableView
var userInfoArray = [UserInfoClass]()
Then, assuming we have a structure like this...
users
uid_0
name: "Leroy"
handicaps
uid_0
amt: 4
fav_foods
uid_0
fav_food: "Pizza"
...here's a function that reads all users, then iterates over each one populating a UserInfoClass with their name and uid, as well as creating a dispatch group that also populates their favorite food and handicap. When that's complete the user is added to the dataSource array and when all of the users are read the tableView is reloaded to display the information.
func loadUsersInfoAndHandicap() {
let ref = self.ref.child("users")
self.userInfoArray = []
ref.observeSingleEvent(of: .value, with: { snapshot in
let group = DispatchGroup()
let allUsers = snapshot.children.allObjects as! [DataSnapshot]
for user in allUsers {
let uid = user.key
let name = user.childSnapshot(forPath: "name").value as? String ?? "No Name"
let aUser = UserInfoClass()
aUser.uid = uid
aUser.name = name
group.enter()
self.loadFavFood(withUid: uid) {favFood in
aUser.favFood = favFood
group.leave()
}
group.enter()
self.loadHandicap(withUid: uid) { handicap in
aUser.handicap = handicap
group.leave()
}
group.notify(queue: .main) {
self.userInfoArray.append(aUser)
}
}
group.notify(queue: .main) {
print("done, reload the tableview")
for user in self.userInfoArray {
print(user.uid, user.name, user.favFood, user.handicap)
}
}
})
}
the users name and uid is read from the main users node and here are the two functions that read their favorite food and handicap.
func loadFavFood(withUid: String, completion: #escaping(String) -> Void) {
let thisUser = self.ref.child("userInfo").child(withUid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let food = snapshot.childSnapshot(forPath: "fav_food").value as? String ?? "No Fav Food"
completion(food)
})
}
func loadHandicap(withUid: String, completion: #escaping(Int) -> Void) {
let thisUser = self.ref.child("handicaps").child(withUid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let handicap = snapshot.childSnapshot(forPath: "amt").value as? Int ?? 0
completion(handicap)
})
}
note that self.ref points to my firebase so substitute a reference to your firebase.
Note I typed this up very quickly and there is essentially no error checking so please add that accordingly.

realm commit write error - Can't commit a non-existing write transaction

I am trying to add a record to a realm DB table.
I have a class Connection which represents a table I need in my DB and have created dynamic vars which are to represent the columns:
import Foundation
import RealmSwift
import Realm
open class ConnectionState: Object {
open dynamic var _id : String = NSUUID().uuidString
open dynamic var a : String = ""
open dynamic var b : String = ""
open dynamic var c : Int = 0
open override class func primaryKey() -> String? {
return "_id"
}
required public init() {
super.init()
}
required public init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required public init(value: Any, schema: RLMSchema) {
fatalError("init(value:schema:) has not been implemented")
}
}
Then in my code I am trying to write and commit the write transaction like so:
let ConnectionState = ConnectionState()
ConnectionState.a = "a"
ConnectionState.b = "b"
ConnectionState.c = 1
try! self.realm.write {
self.realm.add(ConnectionState)
}
try! self.realm.commitWrite()
When running this code, I am receiving the error:
Can't commit a non-existing write transaction
What am I missing? Do I need to have inits in my ConnectionState class?
Before adding in the commitWrite, I was trying to view the db with realm browser. I found my device in xCode and chose to download the container but it was empty. Then I thought I needed to add in commitWrite
In your example you called commitWrite without having called beginWrite. You cannot commit a write transaction because you did not start one. Either start a write transaction or delete the commitWrite line.
Start transaction and commit it
self.realm.beginWrite()
self.realm.add(ConnectionState)
try! self.realm.commitWrite()
Delete commitWrite
try! self.realm.write {
self.realm.add(ConnectionState)
}
The Realm docs have two examples of adding data to the database.
Use the realm.write method
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \(myDog.name)")
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs less than 2 years old
let puppies = realm.objects(Dog.self).filter("age < 2")
puppies.count // => 0 because no dogs have been added to the Realm yet
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Use realm.beginWrite() and realm.commitWrite() to start the write transaction and commit data to the database
let realm = try! Realm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWrite()
// Add row via dictionary. Property order is ignored.
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": Date(timeIntervalSince1970: TimeInterval(idx2))
])
}
// Commit the write transaction
// to make this data available to other threads
try! realm.commitWrite()
}
try! self.realm.write {
self.realm.add(ConnectionState)
}
This code is somewhat equivalent to (possibly with some additional error handling):
realm.beginWrite()
...
try! realm.commitWrite()
Which means you're trying to commit your writes twice.
Just change your code like this:
try! self.realm.write {
self.realm.add(ConnectionState)
}
// try! self.realm.commitWrite()

How to Save to a Custom Join Table Core Data (many-to-many) without unique predicate - ManagedObjectID (Swift)?

I will be super thankful for any help. How can I save instances to a join table without a unique identifier as a predicate? Can I use the managed object id to check if the item exists already?
I'm building an app with different exercise plans. Each plan holds many exercise, and an exercise can belong to many plans. I have structured my data model to include a custom join table so that I can query the completion status of an exercise from within one plan.
I'm sourcing my data from a json file and would like to save it to core data. I'm able to correctly save my CoreExercise, and CorePlan tables, however am having difficulty understanding how to save the instance of the object in the intermediate join table, since I'm unsure of what predicate to use.
I've written a class function to check if the instance exists, and to save it if it doesn't.
class CoreExercisePlan: NSManagedObject {
class func coreExercisesForExercisePlan(exerciseInfo: Exercise, planName: String, inManagedObjectContext context: NSManagedObjectContext) -> CoreExercisePlan? {
let request = NSFetchRequest(entityName: "CoreExercisePlan")
request.predicate = NSPredicate() // Search for ObjectID here? / How?
if let exercisePlan = (try? context.executeFetchRequest(request))?.first as? CoreExercisePlan {
print("we have this exercise plan already saved")
return exercisePlan
} else if let exercisePlan = NSEntityDescription.insertNewObjectForEntityForName("CoreExercisePlan", inManagedObjectContext: context) as? CoreExercisePlan {
exercisePlan.status = 0
exercisePlan.progress = 0
print("we are creating new object")
return exercisePlan
}
return nil
}
private func updateDatabaseWithExercisePlans(){
managedObjectContext?.performBlock {
// Array of exercises for each plan:
let coffeePlanExercises = self.coffeeExercises
let subwayPlanExercises = self.subwayExercises
for exercise in coffeePlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "coffee", inManagedObjectContext: self.managedObjectContext!)
}
for exercise in subwayPlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "subway", inManagedObjectContext: self.managedObjectContext!)
}
do {
try self.managedObjectContext?.save()
} catch let error {
print("printing error here: \(error)")
}
}
}
Is there a way to get the objectID of the instance in the join table, and use that as a predicate? Thanks!

Bypass unique auto ID created by Firebase. Swift 3.0

When I upload an Image, it is automatically update to database child(user.uid).child(newImage).childAutoID.setValue()
This is my json node
Users
WOhpPzlHCzYximWyBXas3Prg2rL2
NewImage
-KSE4ZcvqhFeWmuRQjjI
-KSEGLazKbfPePkCBFbe
-KSEGNVimH7GACRyXbo4
-KSEGO4gfLGOv0erTXA5
-KSENPeq7Oa5cru7gKum
Inside one autoID, This is the Json tree
-KSE4ZcvqhFeWmuRQjjI
DownloadUrl: "abcd.com"
timestamp: 21-09-2016
When I try to get value from DownloadUrl, I use child("Users") but can't child through user uid, anyways I can't child through autoUID which is created by Firebase. Does anyone have any experience. Thank you
Swift 2:
FIRDatabase.database().reference().child("Users/\(FIRAuth.auth()!.currentUser!.uid)/NewImage").observeSingleEventOfType(.Value , withBlock : {(imagesSnap) in
if let imagesDict = imagesSnap.value as? [String:AnyObject] {
for each in imagesDict{
print(each.0) // your autoId
print(each.1) // your imageDetails for that autoId
}
}
})
Swift 3:
FIRDatabase.database().reference().child("Users/\(FIRAuth.auth()!.currentUser!.uid)/NewImage").observeSingleEvent(of: .value, with : {(imagesSnap) in
if let imagesDict = imagesSnap.value as? [String:AnyObject] {
for each in imagesDict{
print(each.0) // your autoId
print(each.1) // your imageDetails for that autoId
}
}
})
But if you are looking for the autoID that contains a specific key value pair Read last part of this answer