How I can get all of users lists from Firestore? - swift

class UsersViewModel : ObservableObject {
#Published var users = [CurrentUser]()
init() {
fetchUserLists()
print(users)
}
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users")
.getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
//success
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
}
}
}
I'm trying to fetch all of users in my firestore database, but unfortunately my users array is empty. I don't know what my mistake is.
Please check my firestore screen shot, and give me tips!
Thank you!

You're having an issue with asynchronous code. Code is faster than the internet and you have to allow time for data to be retrieved from Firebase.
Additionally, Firebase data is only valid within the closure following the Firebase call. In this case your code is attempting to print an array before it's been filled.
Here's the issue
init() {
fetchUserLists() //<-takes time to complete
print(users) //this is called before fetchUserLists fills the array
}
here's the fetchUserLists function with where the print statement should be
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users").getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
print(self.users) //we are within the closure and the array is now populated
//this is a good spot to, for example, reload a tableview
// or update other UI elements that depend on the array data
}
}

Related

How to store a full Firestore query path?

After doing a query to Firestore i'm attempting to store the Firestore path so that i can acces it later.
This way i can do a Firestore query and attach a listener. On the second query i can access that stored path to detach the old listener and attach a new one.
When initializing a app a query is executed.
override func viewDidLoad() {
super.viewDidLoad()
let path = Firestore.firestore().collection("all_users").document(uid).collection("usercollection").whereField("datefilter", isGreaterThan: self.currentDate)
newSearch(firebasePath: path)
However, storing the path is necessary as the query can change in unpredictable ways. Below is a function that changes the query depending on user input.
func filterQuery(filterText:string) {
let path = Firestore.firestore().collection("all_users").document(uid).collection(filterText).whereField("datefilter", isGreaterThan: self.currentDate)
newSearch(firebasePath: path)
A query is made and a listener is attached on that specific query. This way i can grab the data and listen when something is modified to that specific query.
func newSearch(firebasePath: Query) {
//REMOVE OLD LISTENER IF IT EXISTS OR IF QUERY IS THE SAME ---> ADD NEW ONE AT THE END OF FUNCTION
if self.storedFirebasePath != nil {
if self.storedFirebasePath != firebasePath {
let listener =
self.storedFirebasePath.addSnapshotListener { snapshot, error in
}
listener.remove()
}
}
let queryRef = firebasePath
queryRef.getDocuments() { (querySnapshot, err) in
if err == nil && querySnapshot != nil {
if querySnapshot!.documents.count > 0 {
self.mapIsEmpty = false
for document in querySnapshot!.documents {
let data = document.data()
//MANAGING DATA....
}
} else {
print("NO DATA")
}
}
}
}
//STORE FIRESTORE PATH
self.storedFirebasePath = queryRef
//ATTACH LISTENER
self.realTimeUpdates(firebasePath: firebasePath)
}
}
With code below i get "Currently i get the Error = 'init()' is unavailable: FIRQuery cannot be created directly"
var storedFirebasePath = Query()
Maybe the parameters used to create the query can be stored to recreate the path. Or is there a better practice to do what i'm attempting
I don't think what you're trying to do is possible. You can however store a document reference. But even with a document reference you can't detach listeners with it.

Swift problem with Firestore data retrieval [duplicate]

This question already has answers here:
Retrieve data from array in another function
(1 answer)
Is Firebase getDocument run line by line?
(1 answer)
Storing asynchronous Cloud Firestore query results in Swift
(1 answer)
SwiftUI - Wait until Firestore getDocuments() is finished before moving on
(1 answer)
Closed last year.
I have been searching a while and I could not find a solution -> me opening a new thread.
I am making this register page which works with a token send to the users email, it has to be checked against the values in the database, however (in code below) it prints "false" before "hi", I have been trying to get it async, but it doesn't want to work. Can someone help me out? Thanks!
PS: The selection variable makes my app go to the next page in registration, which, because return is false, won't go to.
public static func tokenVerify(_ token: String) -> Bool{
// Get database
let db = Firestore.firestore()
let collection = db.collection("tokens")
let query = collection.whereField("token", isEqualTo: token)
// Make response
var response = false
// Get all documents
query.getDocuments { snapshot, error in
// Check for errors,
if error == nil{
// No error
if let snapshot = snapshot {
if snapshot.isEmpty {
print("No token in database")
} else {
response = true
print("hi")
}
} else {
print("Unknown Error")
}
} else {
print("Error retrieving documents.")
}
}
print(response)
return response
}
Main View:
DispatchQueue.main.async{
if Registration.tokenVerify(token) {
selection = "third"
}
}

Swift / Firestore setData with multiple documents at once causes app to crash

I am creating a fitness app where you can favorite trainer accounts and when you favorite a trainer account I want to add all workouts posted by that trainer to the current user's feed.
When you favorite a trainer account I use this function to add the workouts to a collection on firestore:
func addToUserFeed() {
guard let trainerUid = trainer.id else { return }
guard let currentUid = AuthViewModel.shared.userSession?.uid else { return }
COLLECTION_WORKOUTS.whereField("ownerUid", isEqualTo: trainerUid).addSnapshotListener { snapshot, _ in
guard let workoutIDs = snapshot?.documents.map({ $0.documentID }) else { return }
workoutIDs.forEach { id in
COLLECTION_USERS.document(currentUid).collection("user-feed").document(id).setData([:])
}
}
}
Similarly, when you unfavorite a trainer I am removing those workouts from the user feed collection with this function:
func removeFromUserFeed() {
guard let trainerUid = trainer.id else { return }
guard let currentUid = AuthViewModel.shared.userSession?.uid else { return }
COLLECTION_WORKOUTS.whereField("ownerUid", isEqualTo: trainerUid).addSnapshotListener { snapshot, _ in
guard let workoutIDs = snapshot?.documents.map({ $0.documentID }) else { return }
workoutIDs.forEach { id in
COLLECTION_USERS.document(currentUid).collection("user-feed").document(id).delete()
}
}
}
Then to display these workouts on the feed page view in my app I fetch all the workouts in the user-feed collection on firestore with this function:
//FETCH WORKOUTS SAVED BY THE USER
func fetchFavoriteWorkouts() {
guard let currentUid = AuthViewModel.shared.userSession?.uid else { return }
COLLECTION_USERS.document(currentUid).collection("user-feed").addSnapshotListener { snapshot, _ in
guard let workoutIDs = snapshot?.documents.map({ $0.documentID }) else { return }
//THIS MAY CAUSE AN UNNECCESSARY AMOUNT OF WRITES TO THE APP
self.workouts.removeAll()
workoutIDs.forEach { id in
COLLECTION_WORKOUTS.document(id).addSnapshotListener { snapshot, _ in
guard let workout = try? snapshot?.data(as: Workout.self) else { return }
self.workouts.append(workout)
print("\(workoutIDs)")
}
}
}
}
This is what the firestore collection looks like for user-feed. It adds workout ID documents to the collection that match up with workouts posting by the trainer you just favorited:
Firestore structure
This is working properly in a sense that when you favorite a trainer account it correctly adds multiple documents to the user-feed collection which correspond to workout IDs posted by the trainer you just favorited, however, when you favorite a trainer account the app just closes (not really crashes), and then when you re-open the app the user-feed correctly displays the workouts from trainers you have favorited.
Is there anything in my code that may be causing this random app close when you favorite a trainer?
I understand that this code may not be the most efficient, but I am really not focused on fixing that at the moment, just want to fix the random app close out.
Edit:
The crash only happens when I favorite a trainer that has multiple workouts posted. So I guess something causes the crash when I add multiple documents to a collection at once? Because if I favorite a trainer that only has one workout then it adds that workout to my feed without crashing at all.
the problem is caused by the listener getting called each time a workout is added to workout list and adding duplicated values to the list .
change addSnapshotListener to getDocuments fetchFavoriteWorkouts()
and call this fetch function inside .onAppear() in feed View

How can I add these Firestore fields to a Dictionary?

I am looking to add all my "usernames" into a dictionary. I am having some trouble doing this. I am sure it's very obvious, but I am very new to coding.
I am stuck at, right now and can't seem to find a clear answer anywhere:
func fetchUser() {
let db = Firestore.firestore()
let usernameSearch = db.collection("users")
usernameSearch.getDocuments { (snapshot, error) in
if error != nil {
print("Error obtaining usernames")
} else {
for field in snapshot!.documents {
let field = field.get("username")
print(field!)
}
}
}
}
I would really appreciate it if somebody could help me out. I am sure it's very obvious, or I'm just doing it totally wrong.
First, get into the habit of safely unwrapping over force unwrapping. And choose more accurate names for your objects (i.e. usersCollection over usernameSearch). However, in this case, there's no need to instantiate individual properties for the database and the collection since they're not being used anywhere else but here (so be efficient and omit them).
var usersDictionary = [String: [String]]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // unwrap the snapshot safely
var usernames = [String]()
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usernames.append(username)
}
}
usersDictionary["usernames"] = usernames
} else {
if let error = error {
print(error)
}
}
}
}
Or if you actually meant an array of users:
var usersArray = [String]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // don't force unwrap with !
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usersArray.append(username)
}
}
} else {
if let error = error {
print(error)
}
}
}
}
I'm assuming that what you're looking for is an Array, not a Dictionary. I'll also assume that you are indeed getting the correct value that you'd expect out of field.get("username"), e.g. a string such as "Bob." Therefore, what you are trying to do is map the list of document objects to a list of strings.
If you scroll to the Topics section of the Array documentation from Apple, you can find some of the operations they provide for arrays such as snapshot!.documents.
One of those operations is actually map, and its description is:
Returns an array containing the results of mapping the given closure over the sequence’s elements.
https://developer.apple.com/documentation/swift/array/3017522-map
In other words, you provide a transformation to perform for each instance of a document belonging to the snapshot!.documents Array and get back a new Array containing the resultant values of that transformation.
In this case I will use a more specific operation; compactMap. We have to try and cast the returned value from Any to String. If that does not succeed, it will return nil, and we'll want to filter that out. I expect it to be an unlikely case due to the type requirements made by the Firebase Console, but it's good to be aware of it. Here is the example:
func fetchUsernames(from usernameCollection: String, completion: #escaping ([String]) -> Void) {
let db = Firestore.firestore()
let collection = db.collection(usernameCollection)
collection.getDocuments { snapshot, error in
guard error != nil,
let usernames = snapshot?.documents.compactMap { $0.get("username") as? String }
else { return print("Error obtaining usernames") }
completion(usernames)
}
}
The key line here being let usernames = snapshot?.documents.compactMap { $0.get("username") }. We are passing the map function a closure. This closure is passed an argument itself; each value from the snapshot?.documents array. You may refer to this passed in value with $0.

I cannot get the AWS Cognito credentials of a user (swiftUI)

I have tried a couple of different things, and at this point I am stumped. I simply want to be able to access the user's email to present it in a view. However I have not been able to successfully present, much less retrieve, this information. Here are the two pieces of code I have tried with:
func getUsername() -> String? {
if(self.isAuth) {
return AWSMobileClient.default().username
} else {
return nil
}
}
and
func getUserEmail() -> String {
var returnValue = String()
AWSMobileClient.default().getUserAttributes { (attributes, error) in
if(error != nil){
print("ERROR: \(String(describing: error))")
}else{
if let attributesDict = attributes{
//print(attributesDict["email"])
self.name = attributesDict["name"]!
returnValue = attributesDict["name"]!
}
}
}
print("return value: \(returnValue)")
return returnValue
}
Does anyone know why this is not working?
After sign in try this:
AWSMobileClient.default().getTokens { (tokens, error) in
if let error = error {
print("error \(error)")
} else if let tokens = tokens {
let claims = tokens.idToken?.claims
print("claims \(claims)")
print("email? \(claims?["email"] as? String ?? "No email")")
}
}
I've tried getting the user attributes using AWSMobileClient getUserAttributes with no success. Also tried using AWSCognitoIdentityPool getDetails With no success. Might be an error from AWS Mobile Client, but we can still get attributes from the id token, as seen above.
If you are using Hosted UI, remember to give your hosted UI the correct scopes, for example:
let hostedUIOptions = HostedUIOptions(scopes: ["openid", "email", "profile"], identityProvider: "Google")
It is because it is an async function so will return but later than when the function actually ends with the value. Only way I found to do it is placing a while loop and then using an if condition.