Firebase's ref().child(stringPath: String) returning the entire top level collection - swift

I'm trying to retrieve a specific child of my Firebase database using swiftUI. To do that I use the simple expression
func addListeners() {
let database = Database.database(url: "https://someUrl")
let ref = database.reference(withPath: "users")
let currentUserId = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef = database.reference(withPath: "users/\(currentUserId)/drivingTowardsUsers")
print("Loading data from \(drivingTowardsRef)")
//THIS RIGHT HERE IS CAUSING THE PROBLEM
ref.observe(.childAdded) { snapshot in
print("Got TOP LEVEL data for user \(snapshot.key): \(String(describing: snapshot.value))")
}
//---------------------------------------
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
print("Got arriving user data \(snapshot.key): \(String(describing: userSnapshot.value))")
}
}
}
}
The function will just return the entire database data
EDIT: The function returns the data from the first observer ref top level in this case users/ which in my case has two elements: niixi6iORjNn8gWq6tKvSi3Bxfc2, u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2
Got arriving user data niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
aproxTime = 0;
distance = 0;
latitude = "37.33070704";
longitude = "-122.03039943";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
};
u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2 = {
aproxTime = 0;
distance = 0;
drivingTowardsUsers = {
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
approxTime = 0;
distance = "560.1447571016249";
};
};
latitude = "37.32984184";
longitude = "-122.02018095";
parkingMode = offering;
userId = u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2;
username = cleoBadu;
};
The key for the child path I pass him seems to be correct but it's still returning the entire top level collection instead of the single item...
EDIT: The problem seems to be on the first observer which messes up the .getData() of the ref.child(snapshot.key). Is that even possible?
Just commenting out that ref.observe(.childAdded) will automatically make the second ref.child(snapshot.key) behave totally normally
What am I missing?
I could get the entire database as a single mega dictionary and then get the child I want from there but it doesn't seem really conventional, especially when google's library offers the possibility to not do that.
EDIT: I added a printing statement that prints the url of the database ref. If I then type in the url on my browser, it redirects me on the FRT database and landing me on the correct object. So the url it's generating is correct and works perfectly fine.
Still the object returned by the getData() is the entire db
SN: I removed all codable structs as that is not the problem, so the question is more focused on the actual problem
EDIT: Created a simple view as that. On a clean project it works on my project it doesn't. I guess it's some sort of configuration but's it's hard to look into it.
PROBLEM: Whatever child(string) I pass him it returns the entire top level data either way (replacing so snapshot.key). For example: I pass the key "something" -> all users are returned, I pass the key "" all users are returned

I just tried to reproduce the problem with (mostly) your code and data, but am not getting the same behavior.
I put the equivalent data into a database of mine at: https://stackoverflow.firebaseio.com/68956236.json?print=pretty
And used this code in Xcode 1.2 with Firebase SDK version 8.6.1:
let ref: DatabaseReference = Database.database().reference().child("68956236")
let currentUserId: String = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef: DatabaseReference! = ref.child("\(currentUserId)/drivingTowardsUsers");
print("Loading data from \(drivingTowardsRef)")
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
do {
//let parkingUser = try userSnapshot.data(as: ParkingUser.self)
print("Got data for user \(snapshot.key): \(String(describing: userSnapshot.value))")
} catch {
print("There has been an error while decoding the user location data with uid \(snapshot.key), the object to be decoded was \(userSnapshot). The decode failed with error: \(error)")
}
}
}
}
The output I get is:
Loading data from Optional(https://stackoverflow.firebaseio.com/68956236/u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2/drivingTowardsUsers)
2021-08-27 10:39:09.578043-0700 Firebase10[36407:3458780] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Got data for user niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
aproxTime = 0;
distance = 0;
latitude = "37.32798355";
longitude = "-122.01982712";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
})
As far as I can see this behavior is correct, but different from what you get. I hope knowing that I don't see the same behavior, and what versions I use, may be helpful to you.

This is not an issue with Firebase but rather client-side handling of the data returned, You’re expecting a Double within your Codable struct but supplying a String in the other end— Can you try:
public struct ParkingUser: Codable {
var latitude: String
var longitude: String
}

Related

How can I make retrieving data from firestore cloud faster when refreshing the page [duplicate]

I am new in programming and in iOS development. I am trying to make an app using Firestore database from Firebase. I don't know if it is normal or not, but when I am trying to get a data from firestore database, it seems too long for me. I don't know if I make a mistake or not
here is my code to get all city data from firestore
reference :
import Foundation
import FirebaseFirestore
import Firebase
enum FirestoreCollectionReference {
case users
case events
case cities
private var path : String {
switch self {
case .users : return "users"
case .events : return "events"
case .cities : return "cities"
}
}
func reference () -> CollectionReference {
return Firestore.firestore().collection(path)
}
}
I use getAllCitiesDataFromFirestore method in CityKM class to get the city data that stored in firestore
class CityKM {
var name : String
var coordinate : GeoPoint
init (name: String , coordinate: GeoPoint ) {
self.name = name
self.coordinate = coordinate
}
init (dictionary: [String:Any]) {
// this init will be used if we get data from firebase observation to construct an event object
name = dictionary["name"] as! String
coordinate = dictionary["coordinate"] as! GeoPoint
}
static func getAllCitiesDataFromFirestore (completion: #escaping ( [CityKM]? )->Void) {
// to retrieve all cities data from Firebase database by one read only, not using realtime fetching listener
let startTime = CFAbsoluteTimeGetCurrent() // to track time consumption of this method
FirestoreCollectionReference.cities.reference().getDocuments { (snapshot, error) in
if let error = error {
print("Failed to retrieve all cities data: \(error.localizedDescription)")
} else {
print("Sucessfully get all cities data from firestore")
guard let documentsSnapshot = snapshot, !documentsSnapshot.isEmpty else {
completion(nil)
return
}
let citiesDocuments = documentsSnapshot.documents
var cityArray = [CityKM]()
for document in citiesDocuments {
guard let cityName = document.data()["name"] as? String,
let cityCoordinate = document.data()["coordinate"] as? GeoPoint else {return}
let theCity = CityKM(name: cityName, coordinate: cityCoordinate)
cityArray.append(theCity)
}
completion(cityArray)
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime // to track time consumption of this method
print("Time needed to get all cities data from Firestore : \(timeElapsed) s.") // to track time consumption of this method
}
}
}
}
extension CityKM {
// MARK: - User Helper Methods
func toDictionary() -> [String:Any]{
return [
"name" : name,
"coordinate" : coordinate
]
}
}
from my debugging area, it is printed
"Time needed to get all cities data from Firestore : 1.8787678903 s."
is it possible to make it faster? Is 1.8s normal? am i make a mistake in my code that make the request data takes too long time ? I hope that I can make request time is below one second
I don't think the internet speed is the problem, since I can open video on youtube without buffering
That performance sounds a bit worse than what I see, but nothing excessive. Loading data from the cloud simply takes time. A quick approach to hide that latency is by making use of Firebase's built-in caching.
When you call getDocuments, the Firebase client needs to check on the server what the document's value is before it can call your code, which then shows the value to the user. As said: there is no way to speed up this reading in your code, so it'll always take at least 1.8s before the user sees a document.
If instead you listen for realtime updates from the database with addSnapshotListener, the Firebase client may be able to immediately call your code with values from its local cache, and then later re-invoke your code in case there has been an update to the data on the server.

Ambiguous reference to member 'save(_:completionHandler:)' with CloudKit save attempt

I'm trying to save back to CloudKit after updating a reference list and getting the error on the first line of this code block.
Error: Ambiguous reference to member 'save(_:completionHandler:)'
CKContainer.default().publicCloudDatabase.save(establishment) { [unowned self] record, error in
DispatchQueue.main.async {
if let error = error {
print("error handling to come")
} else {
print("success")
}
}
}
This sits within a function where the user going to follow a given location (Establishment). We're taking the existing establishment, and its record of followers, checking to see if the selected user is in it, and appending them to the list if not (or creating it if the list of followers is null).
Edit, in case helpful
//Both of these are passed in from the prior view controller
var establishment: Establishment?
var loggedInUserID: String?
#objc func addTapped() {
// in here, we want to take the logged in user's ID and append it to the list of people that want to follow this establishment
// which is a CK Record Reference
let userID = CKRecord.ID(recordName: loggedInUserID!)
var establishmentTemp: Establishment? = establishment
var followers: [CKRecord.Reference]? = establishmentTemp?.followers
let reference = CKRecord.Reference(recordID: userID, action: CKRecord_Reference_Action.none)
if followers != nil {
if !followers!.contains(reference) {
establishmentTemp?.followers?.append(reference)
}
} else {
followers = [reference]
establishmentTemp?.followers = followers
establishment = establishmentTemp
}
[this is where the CKContainer.default.....save block pasted at the top of the question comes in]
I've looked through the various posts on 'ambiguous reference' but haven't been able to figure out the source of my issue. tried to explicitly set the types for establisthmentTemp and followers in case that was the issue (based on the solutions to other related posts) but no luck.
Afraid I'm out of ideas as a relatively inexperienced newbie!
Help appreciated.
Documenting the solution that I figured out:
Combination of two issues:
I was trying to save an updated version of a CK Record instead of updating
I was not passing a CK Record to the save() call - but a custom object
(I believe point two was the cause of the 'ambiguous reference to member'
error)
I solved it by replacing the save attempt (first block of code in the question) with:
//first get the record ID for the current establishment that is to be updated
let establishmentRecordID = establishment?.id
//then fetch the item from CK
CKContainer.default().publicCloudDatabase.fetch(withRecordID: establishmentRecordID!) { updatedRecord, error in
if let error = error {
print("error handling to come")
} else {
//then update the 'people' array with the revised one
updatedRecord!.setObject(followers as __CKRecordObjCValue?, forKey: "people")
//then save it
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in
}
}
}

Core Data Object was written to, but never read

As I try to update an existing entry in my Core Data DB, I fetch the desired item by id, change it to a new item and save in context.
However, when I fetch the object and replace it, I get the warning "Core Data Object was written to, but never read." It does make sense since I'm not really using that object, but as I understand it, just giving it a value saves it in Core Data.
static var current: User? {
didSet {
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
CoreDataManager.saveInContext()
}
}
static func get(with id: String?) -> User? {
guard let id = id else { return nil }
let request: NSFetchRequest = User.fetchRequest()
let predicate = NSPredicate(format: "id = %#", id)
request.predicate = predicate
do {
let users = try CoreDataManager.managedContext.fetch(request)
return users.first
} catch let error {
print(error.localizedDescription)
return nil
}
}
I want to make sure, is this the recommended process to overwrite a value in Core Data, or am I doing something wrong?
This section
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
seems just updating local variable userInCoreData, not User object in Core Data.
So the warning says "you fetched data from core data and set to a variable, but you set another value to the variable soon, never use the first value from core data. Is it OK?"
What you really want to do is something like this?
if var userInCoreData = User.get(with: current?.id), let current = current {
userInCoreData.someValue = current.someValue
userInCoreData.anotherValue = current.anotherValue
}

Unexpectedly unwrapping an optional to find a nil after an API call to Spotify

So I know this may be a bit specific but I've been staring at my code and am unable to resolve this issue. Basically, I'm making a network call to spotify to obtain a certain playlist and pass a number that will ultimately determine the number of songs I get back. The code is basically as follows:
// A network call is made just above to return somePlaylist
let playlist = somePlaylist as! SPTPartialPlaylist
var songs: [SPTPartialTrack] = []
// load in playlist to receive back songs
SPTPlaylistSnapshot.playlistWithURI(playlist.uri, session: someSession) { (error: NSError!, data: AnyObject!) in
// cast the data into a correct format
let playlistViewer = data as! SPTPlaylistSnapshot
let playlist = playlistViewer.firstTrackPage
// get the songs
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(playlist.items.count)))
songs.append(playlist.items[random] as! SPTPartialTrack)
}
}
The problem comes at the portion of code that initializes random. In maybe 1 in 20 calls to this function I, for whatever, reason unwrap a nil value for playlist.items.count and can't seem to figure out why. Maybe it's something I don't understand about API calls or something else I'm failing to see but I can't seem to make sense of it.
Anyone have any recommendations on addressing this issue or how to go about debugging this?
Ok, after sleeping on it and working on it some more I seem to have resolved the issue. Here's the error handling I implemented into my code.
if let actualPlaylist = playlist, actualItems = actualPlaylist.items {
if actualItems.count == 0 {
SongScraper.playlistHasSongs = false
print("Empty playlist, loading another playlist")
return
}
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(actualItems.count)))
songs.append(actualPlaylist.items[random] as! SPTPartialTrack)
}
completionHandler(songs: songs)
}
else {
print("Returned a nil playlist, loading another playlist")
SongScraper.playlistHasSongs = false
return
}

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.