Efficiently storing and getting likes in FireStore / Document DB - firebase

I have a page with posts and likes for each post.
In FireStore a collection of posts and a collection of likes, and I update the total_likes and recent likes array when a user likes or unlikes a post with cloud functions.
However, I can't figure out how to show for each post if the currently logged in user liked it or not. What's an efficient way to do that for.
Any pointers?

I believe you might need to look at data aggregration. Even though this example is with Angular, I also use the same principle in a different application: https://angularfirebase.com/lessons/firestore-cloud-functions-data-aggregation/
Alternatively, you could store the post_id's that your user likes in their own 'like_array'. Knowing which posts the user currently sees, you can cross reference the shown post_id's with the (single object) 'like_array' from the user to determine if he/she has liked a particular post. In the long run, you could disambiguate like_arrays based on days or weeks, and only query the like_arrays of this and last day/week - based on what post you are showing. If you are working with categories of posts, similar applies: different like_arrays for different categories.
Hope this helps!

One solution would be to have another collection in your Firestore database where you create a document by user, in which you save (and update) an object containing all the posts this user has liked.
Like
- likers (Collection)
- UserUID (doc)
- postIds {
post1_UID: true,
post2_UID: true
}
The idea is to use the technique described in the doc, here: https://firebase.google.com/docs/firestore/solutions/arrays#solution_a_map_of_values
I don't know which language you use in the front end but in JavaScript you would do:
var postToTestId = ....; <- You set this value as you need (e.g. as a function parameter)
firebase.auth().signInWithEmailAndPassword("...", ".....")
.then(function (info) {
var postId = 'azer';
return db.collection('likers')
.where('postIds.'+ postToTestId, '==', true)
.get();
})
.then(function(querySnapshot) {
if (querySnapshot.size > 0) {
console.log("USER LIKES THIS POST!!!");
}
})
.catch(function (error) {
console.log(error);
});
I don't think there is any solution without storing somewhere all the posts each user liked...

Related

Upsert in Firebase Firestore without the onFailure callback

There must be a better way to make upsert in Firebase Firestore in Kotlin.
I have collection of users that contains another collection userDocuments that contains field called highlights containing list of highlights.
I cannot use set and merge options as that will override the highlights list.
Any ideas how to make the code better. I do not like making two database requests on create and handling the failure like this. Maybe my database structure can be also optimized but I thought it is smart as all private userData will be stored in users collections with some subcollections.
My database structure is like this:
users -> {userId} -> userDocuments -> {docId} -> highlights ["this will be highlighted"]
users, and userDocuments are collections. Highlights is a field on userDocument.
docId might not yet be there, there will be 1000 of documents. And I do not want to add it to every user. I want it to be there, only when they make a change such as add or remove highlight to list of highlights.
usersCollection
.document(userId)
.collection("userDocuments")
.document(docId)
.update("highlights", FieldValue.arrayUnion(text))
.addOnFailureListener { err ->
// TODO should be handled differently
if (err is FirebaseFirestoreException &&
err.code === FirebaseFirestoreException.Code.NOT_FOUND
) {
val highlights = listOf(text)
usersCollection
.document(it)
.collection("userDocuments")
.document(docId)
.set(mapOf("highlights" to highlights), SetOptions.merge())
}
}
You can update using dictionary notation or dot notation too.
https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects
db.collection("userDocuments")
.document(docId)
.update({
"highlights": FieldValue.arrayUnion(text)
});
You can consider using transactions as I mentioned in the comment above. But not sure if that is what you are looking for.

How to query documents from a sub-collection, based on a main collection's field and a sub-collection's fields in cloud firestore?

I am new to firebase cloud firestore and need help understanding it a bit.
I am working on a project of the following structure.
Each "Restaurant" document contains its own "products" subcollection.
Here, I wanted to run a query to get all the products by different
restaurants that contain the tag "coffee" and are in a specific
Pincode "234144".
I tried Collection group queries by adding Pincode to each product owned by a particular restaurant but changing a Pincode would cost a lot, as all products would have to be edited, I guess.
Is there any efficient way of doing it or is it not possible in this
database in an efficient way?
Please let me know what do you think... Thank you.
If I understand correctly, you want to retrieve all products with a certain tag and pincode (I suppose this is similar to a postal zipcode?). There really are only two alternatives that I can think of:
Collection group query
As you mention, you can store both tag and pincode in product documents. Then perform a single collection group query along these lines (pardon the javascript, but I am not familiar with Dart, it should be very similar):
var products = await firestore.collectionGroup('products')
.where('tag', '==', 'coffee')
.where('pincode', '==', '234144').get();
As you have noted, with this solution you need to keep pincode in each product This piece of data is duplicated and it is normal to feel that it should be avoided because it is wasteful and dangerous (can go out of sync), but this is the way to go! It is called denormalization. This is well explained in this video by Todd Kerpelman.
You can then create a Cloud Function triggered by restaurant update to keep pincode in products in sync with the corresponding pincode in restaurants
Query restaurants then products
To keep the pincode in restaurants only, you have to do your query in two steps: first filter restaurants in the certain pincode, then filter products by tag:
// 1 - retrieve restaurants in specific pincode
var restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag
var products = [];
for(let i = 0; i < restaurants.docs.length; ++i) {
var p = await restaurants.docs[i].collection("products").where("tag", "==", "coffee");
products.push(p);
}
With this method, no need to duplicate pincode in each product, however your queries are less optimal, because you load potentially useless restaurants that do not serve coffee in your pincode.
I have a similar data structure, and Louis Coulet's answer helped me a lot. However, I ran into an issue, as my node/firebase environment complained that restaurants.docs[i].collection was not a function. Instead, I had to use restaurants.docs[i].ref.collection.
So, in my case, here's the code that wound up working (using the original poster's "restaurants/products" data model as an example):
// 1 - retrieve restaurants in specific pincode
const restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag 'coffee'
const products = [];
for (let i = 0; i < restaurants.docs.length; i++) {
await restaurants.docs[i].ref
.collection('products')
.where('tag', '==', 'coffee')
.get()
.then(s => Promise.all(s.docs.map(d => {
products.push(d);
})));
}
I have found that I need to return the sub-collection contents via some kind of promise, otherwise, after I try to get the data in the variable -- products, in this case -- it just shows up as undefined.
After I populate the products variable, I am then able to read its contents like this:
products.forEach(p => {
console.log(p.data().name);
console.log(p.data().tag);
}
I hope someone finds this useful, and many, MANY thanks to Louis Coulet for sending me/us down the correct path with this sub-collection headache :-)

Create documents, sub collections in Firestore via flutter on screen loads

I want to achieve is when flutter screen loads a document should create in firestore in following order.
Document > Sub Collection > Document > Data Fields
I manage to create documents and sub collections in above order, but the first Document appear in italic. That's because the child collection, documents creating before parent document created.
But I couldn't able to fix the issue. I've modified the code now it's not even creating the document. Before this It created in italic mode. Now it's not at all.
Here is the code.
getCurrentUser().then((user) {
DocumentReference todayReference = firestoreInstance.collection('attendance').document(todayDate);
firestoreInstance.collection('profiles').where('user_id', isEqualTo: user).snapshots().listen((onData) {
onData.documents.forEach((f) {
CollectionReference todaySubCollection = todayReference.collection(f.documentID);
DocumentReference attendanceReference = todaySubCollection.document(f["name"].toString().toLowerCase());
Map<String,dynamic> mapData = new Map<String,dynamic>();
mapData['attendance_status'] = true;
mapData['in'] = true;
mapData['out'] = true;
firestoreInstance.runTransaction((transaction) async {
await transaction.set(attendanceReference, mapData);
});
});
});
});
Here getCurrentUser() is returning the logged in user id.
Each profiles assigned to a user.
So, What I'm trying to do is, once user logged in a document should create under attendance collection named today's date.
Then looping through each profiles where user_id is matched with logged in user, the matching results will be store as sub collection under today's date with profiles name field.
Then under the name (document), a transaction needs to run to set details like attendance_status, in & out.
Following images will show how previously documents created.
I need to find a way to create documents, collection without in italic mode. Any help would be appreciated.
"Italicized" documents are virtual/non-existent as mentioned in the docs. If a document only has a sub-collection, it will be a virtual/non-existent document. A workaround for this is by writing fields in the document, like what you've mentioned in the comments.

Modeling and publishing a follower-based feed with Meteor

I'm working on a simple app where a User can follow other users. Users can star posts. And a user's feed is composed of posts that have been starred by users they follow. Pretty simple actually. However, this all gets complicated in Mongo and Meteor...
There are basically two way of modeling this that I can think of:
A user has a property, following, which is an array of userIds that the user follows. Also, a post has a property, starrers, which is an array of userIds that have starred this post. The good thing about this method is that publications are relatively simple:
Meteor.publish 'feed', (limit) ->
Posts.find({starrers: {$in: Meteor.users.findOne(#userId).following}}, {sort: {date: -1}, limit:limit})
We aren't reactively listening to who the user is following, but thats not too bad for now. The main problem with this approach is that (1) the individual documents will become large and inefficient if 1000000 people star a post. Another problem is that (2) it would be pain to keep track of information like when a user started following another user or when a user starred a post.
The other way of doing this is having two more collections, Stars and Follows. If a user stars a post, then we create a document with properties userId and postId. If a user follows another user, then we create a document with properties userId and followId. This gives us the advantage of smaller document sizes for Users and Posts, but complicated things when it comes to querying, especially because Mongo doesn't handle joins!
Now, I did some research and people seem to agree that the second choice is the right way to go. Now the problem I'm having is efficiently querying and publishing. Based on the Discover Meteor chapter about Advanced Publications, I created a publication that publishes the posts that are starred by user's followers -- sorted, and limited.
# a helper to handle stopping observeChanges
observer = (sub, func) ->
handle = null
sub.onStop ->
handle?.stop?()
() ->
handle?.stop?()
handle = func()
Meteor.publish 'feed', (limit) ->
sub = this
userId = #userId
followIds = null
eventIds = null
publishFollows = observer sub, () ->
followIds = {}
Follows.find({userId:userId}).observeChanges
added: (id, doc) ->
followIds[id] = doc.followId
sub.added('follows', id, doc)
publishStars()
removed: (id) ->
delete followIds[id]
sub.removed('follows', id)
publishStars()
publishStars = observer sub, () ->
eventIds = {}
Stars.find({userId: {$in: _.keys(followIds)}).observeChanges
added: (id, doc) ->
eventIds[id] = null
sub.added('stars', id, doc)
publishEvents()
removed: (id) ->
delete eventIds[id]
sub.removed('stars', id)
publishEvents()
publishEvents = observer sub, () ->
Events.find({_id: {$in: _.keys(eventIds)}}, {sort: {name:1, date:-1}, limit:limit}).observeChanges
added: (id, doc) ->
sub.added('events', id, doc)
changed: (id, fields) ->
sub.changed('events', id, fields)
removed: (id) ->
sub.removed('events', id)
While this works, it seems very limited at scale. Particularly, we have to compile a list of every starred post by every follower. The size of this list will grow very quickly. Then we do a huge $in query against all posts.
Another annoyance is querying for the feed on the client after we subscribe:
Meteor.subscribe("feed", 20)
posts = null
Tracker.autorun ->
followers = _.pluck(Follows.find({userId: Meteor.userId()}).fetch(), "followId")
starredPostIds = _.pluck(Stars.find({userId: {$in: followers}}).fetch(), "postId")
posts = Posts.find({_id: {$in: starredPostIds}}, {sort: {date: -1}, limit: 20}).fetch()
Its like we're doing all this work twice. First we do all the work on the server to publish the feed. Then we need to go through the exact same logic again on the client to get those posts...
My question here is a matter of design over everything. How can I efficiently design this feed based on followers staring posts? What collection / collection schemas should I use? How should I create the appropriate publication? How can I query for the feed on the client?
So it turns out that Mongo and "non-relational" databases simply aren't designed for relational data. Thus, there is no solution here with Mongo. I've ended up using Neo4j, but SQL would work fine as well.
meteor add reywood:publish-composite
Meteor.publishComposite('tweets', function(username) {
return {
find: function() {
// Find the current user's following users
return Relationships.find({ follower: username });
},
children: [{
find: function(relationship) {
// Find tweets from followed users
return Tweets.find({user: relationship.following});
}
}]
}
});
Meteor.publish('ownTweets', function(username) {
return Tweets.find({user: username});
});

Meteor user name based on userId

I am saving logged in userId with each record saved in my Meteor app collection as shown in the example below, yet I was wondering if there was any way in Meteor where I can retrieve user name based on the user saved id without have to make another query on the users collection? In Node.js / mongoose there was this Populate function, but I can't seem to find similar package / function in Meteor. So I was wondering if someone can help me by suggesting a resolution to this problem (if any). thanks
var newInvoice = {
customerid: $(e.target).find('[name=customer]').val(),
userid: Meteor.userId(),
//....more fields here
}
Meteor.call('saveInvoice', newInvoice, function(error, id){
if(error)
return alert(error.reason);
});

Resources