I am trying to retrieve posts from a list of accounts that a user follows, but am unsure on the best way to do so? I have implemented a the same structure from this stack overflow answer, as I found it sensible for what I was trying to create (see below).
Firestore-root
|
--- users (collection)
| |
| --- uid (documents)
| |
| --- name: "User Name"
| |
| --- email: "email#email.com"
|
--- following (collection)
| |
| --- uid (document)
| |
| --- userFollowing (collection)
| |
| --- uid (documents)
| |
| --- uid (documents)
|
--- posts (collection)
|
--- uid (documents)
|
--- userPosts (collection)
|
--- postId (documents)
| |
| --- title: "Post Title"
| |
| --- date: September 03, 2018 at 6:16:58 PM UTC+3
|
--- postId (documents)
|
--- title: "Post Title"
|
--- date: September 03, 2018 at 6:16:58 PM UTC+3
However, I am struggling to understand the best way to retrieve a list of posts where its document UID is the same as the one a user is following.
I have attempted to get all the users followers and then for each document, loop over it using another get, but haven't found a successful solution. An example of this code can be seen below:
var userFollowingList = [User]()
db.collection("Following").document(currentUserUID).collection("userFollowing")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion(false)
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
userFollowingList = querySnapshot!.documents.compactMap { querySnapshot -> User? in
return try? querySnapshot.data(as: User.self)
}
}
for user in userFollowingList {
db.collection("Posts")
.getDocuments() { (tripQuerySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion(false)
} else {
for document in tripQuerySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.followingTrips = querySnapshot!.documents.compactMap { querySnapshot -> Trip? in
return try? querySnapshot.data(as: Trip.self)
}
}
}
}
}
}
What is the most efficient way to achieve this? Thanks.
30/03/2020 UPDATE:
I ended up looping over each userID in the following list and returning all the posts for the users, but I'm still not sure if this is the best way. See example below:
let userFollowingIDs = ["01","02","03"]
for id in userFollowingIDs {
db.collection("Trips").document(id).collection("userPosts")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion(false)
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.followingTripsList = querySnapshot!.documents.compactMap { querySnapshot -> Trip? in
return try? querySnapshot.data(as: Trip.self)
}
}
completion(true)
}
}
}
I think that this can be done easier. Of course I do not have details of whole system, however looking at this particular problem I would add to every userPosts document new field followedBy. The field would be an array where all following the post user ids (uid) will be stored.
Now checking, if particular posts is followed by particular user can be done by checking if this followedBy array contains this user id. Then it will be possible to use two Firestore features: collectionGroup and arrayContains filter
Then getting list of posts followed by particular user will be extremely simple like this:
db.collectionGroup("userPosts")
.whereField("followedBy", arrayContains: uid)
.getDocuments() { (snapshot, error) in
// ...
No looping and less document got from the Firestore. This should be more effective when it comes to usage cost as well.
Related
I need to get ID of document in Reservations collection. I don't know ho to do that. When I want to overwrite data in specific Reservation it can't because I don't have ID of the reservation. I am putting their UID of user that is wrong.
guard let uid = Auth.auth().currentUser?.uid else { return }
let userCollection = Firestore.firestore().collection("Users")
let thisUserDoc = userCollection.document(uid)
let snapshot = thisUserDoc.collection("Reservations").document(uid).updateData(data) { err in
if let err = err {
print("Error updating document: \(err) ")
}
else {
print("Document successfully updated")
}
}
Error message - wrong path
Database scructure
As shown in your code and in the error shared in your question you use the same document ID for the doc in the users collection and for the doc in the Reservations (sub)collection. The database screenshot shows that it cannot work: there isn’t any doc corresponding to this case.
You need to use an existing ID for the doc in the Reservations (sub)collection.
If you don’t know the desired ID you can maybe build a query based on some specific field(s) of the doc.
I have some data which is in table format (generated using Go https://github.com/olekukonko/tablewriter) API, I am trying to print the table content as it is on the confluence wiki from the rest API. But while passing it to the body Its format got disturbed and not able to print the table. Please suggest how to do the same for this use case.
Output:
+------------+------------+-------------------+ | name | sample | sample2 | +------------+------------+-------------------+ | test-1 | 20 | 1 | | test_4 | 20 | 1 | +------------+------------+-------------------+
**data**:
+------------+------------+-------------------+
| Name | sample | sample2 |
+------------+------------+-------------------+
| test-1 | 20 | 1 |
| test_4 | 20 | 1 |
+------------+------------+-------------------+
type Payload struct {
Type string `json:"type"`
Title string `json:"title"`
Space Space `json:"space"`
Version Version `json:"version"`
Body Body `json:"Body"`
}
type Space struct {
Key string `json:"key"`
}
type Version struct {
Number int `json:"number"`
}
type Storage struct {
Value string `json:"value"`
Representation string `json:"representation"`
}
type Body struct {
Storage Storage `json:"storage"`
}
func UpdateWiki(data string) int {
Payload_data := Payload{
Type: "page", Title: "",
Space: Space{Key: ""},
Version: Version{Number: 4},
Body: Body{Storage{Value: data, Representation: "storage"}},
}
payloadBytes, err := json.Marshal(Payload_data)
if err != nil {
fmt.Println(err)
}
body := bytes.NewReader(payloadBytes)
req, err := http.NewRequest("PUT", "https://wiki.xyz.io/rest/api/content/xxxxx", body)
//fmt.Println(err)
dump, err := httputil.DumpRequest(req, true)
fmt.Printf("%s", dump)
if err != nil {
fmt.Println(err)
}
req.SetBasicAuth("", "")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
fmt.Println(resp)
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
return resp.StatusCode
}
Currently my Firebase database is using the following structure
Users
User 1 Data
User 2 Data...
However I have created a new collection inside of the "users" collection called "products" which will store product details that users have uploaded on the application.
How can I ensure that once a user uploads a new product, it is only uploaded into their 'User X data' dataset, inside the respective "products" collection. The code I currently have only uploads the data into the "users" collection, with no reference of the users who added the product as required. I have shown the structure of my Firebase database below for reference.
Here is my code:
let user = Auth.auth().currentUser
db.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let userId = data["uid"] as! String
if userId == user?.uid {
db.collection("users").document("products").setData(["productNameField":firstName, "productURLField":lastName,"productPriceField":ebayName, "productDescriptionField":etsyName, "productTimeRemainingField":email])
}
}
}
}
How would I go about updating my code to achieve this?
I think you're looking for
let user = Auth.auth().currentUser
db.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let userId = data["uid"] as! String
if userId == user?.uid {
document.reference.collection("products").addDocument(["productNameField":firstName, "productURLField":lastName,"productPriceField":ebayName, "productDescriptionField":etsyName, "productTimeRemainingField":email])
}
}
}
}
So here document is the DocumentSnapshot that you're looping over, so document.reference gives you the reference to that specific document, and document.reference.collection("products") then points to the `products subcollection for that specific document.
You're wastefully looping over the entire list of users to FIND the user document needed. Simplify, simplify - use the user.uid as the Id of the user document!! (i.e. when you create the user document, save it as
db.collection("users").doc(user.uid).set({whatever})
...then it's trivial to access as...
let user = Auth.auth().currentUser
db
.collection("users")
.doc(user.uid)
.collection("products")
.setData([
"productNameField":firstName,
"productURLField":lastName,
"productPriceField":ebayName,
"productDescriptionField":etsyName,
"productTimeRemainingField":email
]);
If there is another reason to keep the docID and the uid separate (not shown here), then use a query to get the SPECIFIC document with the uid, rather than downloading ALL of them
let user = Auth.auth().currentUser
db
.collection("users")
.where(field: "uid", opStr:"==", value: user.uid)
.getDocuments( { (snapshot, error) in
if let error = error {
print(error)
return
} else { //the query should only return the one document, but queries always return an array
snapshot
.documents[0]
.ref()
.document("products")
.setData([
"productNameField":firstName,
"productURLField":lastName,
"productPriceField":ebayName,
"productDescriptionField":etsyName,
"productTimeRemainingField":email
])
}
}
...etc...
I don't generally use Swift, so the where clause may require different formatting, but the idea is GET ONLY THE DOCUMENT YOU NEED. Your security rules should only be allowing this, anyway.
I am trying to get the three most recent documents from my collection group userPosts using the following database structure and query:
-posts
-{userID}
-userPosts
-{documentID}
-postTime(Field)
Query:
postQuery = Firestore.firestore()
.collectionGroup("userPosts")
.order(by: "postTime", descending: true)
.limit(to: 3)
function used to query Firestore:
func loadPosts() {
postQuery.getDocuments{ [weak self](querySnapshot, error) in
self!.q.async{
var postsTemp = self?.postArray
for doc in querySnapshot!.documents{
self?.documents += [doc]
let post = self!.createPost(doc)
if(!self!.postArray.contains(post)){
postsTemp?.insert(post, at: 0)
}
DispatchQueue.main.async {
self!.postArray = postsTemp!
self!.tableView.reloadData()
}
}
}
}
}
However when I run this I get an error due to the fact that querySnapshot is nil. I am not sure why this happens since when I change descending to false I get a result but in the opposite order that I want. I have a feeling it has something to do with my query but am not sure where I went wrong.
The getDocuments callback gets called with two values (querySnapshot and `error), only one of which will have a value.
You're ignoring the error and assuming that querySnapshot has a value, which not only leads to the error you get, but also hides the likely cause.
I recommend following the pattern used in this example from the Firestore documentation on getting documents:
db.collection("cities").whereField("capital", isEqualTo: true)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
By logging the error you can see what went wrong with your getDocuments() call.
#IBAction func NextButtonTapped(_ sender: Any) {
//validate the fileds
let Error = validateFields()
if Error != nil {
// there is somthing wrong with the fields show error message
showError(Error!)
}
else {
// create cleaned versions of the data
let Password = PasswordTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Email = EmailTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Firstname = FirstnameTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Lastname = LastnameTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
let Age = AgeTextField.text!.trimmingCharacters(in:
.whitespacesAndNewlines)
// create the user
Auth.auth().createUser(withEmail: Email, password: Password) {
(results, Err) in
// check for errors
if Err != nil {
// there was an error creating the user
self.showError("Error creating user")
}
else {
// user was created succesfully store user info
let db = Firestore.firestore()
db.collection("users").document(results!.user.uid).setData(["first
name":Firstname, "last name":Lastname, "age":Age,
"uid":results!.user.uid]) { (Error) in
if Error != nil {
// show error message
self.showError("error saving user data")
}
}
//transition to the home screen
self.transitionToHome()
}
}
}
}
So basically here I am authenticating the user on firebase. (I made
the document ID = the user ID) then I am entering the users info
into the firebase database into a document where its ID is also
equal to the users ID from when they are authenticated. Now what I
am trying to do is to create or get a reference to the document ID
where some of the users info is already stored like name, last name,
age... so I can later add/merge more info into that same document
the name, last name and age is stored under. This is how I am trying
to merge the info together in a diffrent view controller
db.collection("users").document(*******).setData(["middle
Name":Middlename, "favourite colour":Favouritecolour], merge: true)
{ (Error) in
if Error != nil {
// show error messgae
self.showError("error saving user data")
}
}
Where I put "*******" is where I am supposed to reference the
document ID so I can merge/add this info into the same document as
the other users information where the name, last name and age is
stored.
The code I showed you and asked about earlier on how to get a
document ID from, was code I found on stack overflow where they guy
had a similar problem as mine. He was trying to access something out
of his document but I am just trying to create a reference to the
document ID.
The code form earlier:
func getDocument() {
//get specific document from current user
let docRef =
Firestore.firestore().collection("users").document(Auth.auth().currentUs er?.uid ?? "")
//get data
docRef.getDocument { (document, Error) in
if let document = document, document.exists {
let dataDescription = document.data()
print(dataDescription?["uid"])
} else {
print("Document does not exist")
}
}
}
But I don't know if this code will help me, I just thought it might
because its also accessing the document, thats why I though it might
be my answer to getting the document ID.
So basically my question is what do I need to do wether its adding
to the code I found on this site, or if I have to write my own code,
so I can get a reference to the Document ID where my name, last name
and Age is stored, so I can merge more Info into that document
Thank You Very Much!!!
To print the document ID do something like this:
let docRef = Firestore.firestore().collection("users").document(Auth.auth().currentUser?.uid ?? "")
// Get data
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data()
print(dataDescription?["firstname"])
print(document.documentID)
} else {
print("Document does not exist")
}
}
I highly recommend spending some time in the Firestore documentation, as this is covered in there under getting multiple documents from a collection