I'm a Front-End Developer and for the first-time, I'm using Firebase to build an application. I read some documentation/articles and watched this interesting video about foreign keys (many to many relationship): https://www.youtube.com/watch?v=ran_Ylug7AE&list=PLl-K7zZEsYLlP-k-RKFa7RyNPa9_wCH2s&index=2.
I'm trying to apply this approach to retrieve events for a specific user. Ideally, I want to get this information without requesting/paying for too much data. First, this is how I set-up the following database structure (see video):
{
users:
"abc": {
firstname: "Maxime",
...
},
"def": {
firstname: "John",
...
},
},
events: {
"eventAbc-": {
title: "Some great event",
...
},
"eventDef": {
title: "Another great event",
...
}
},
eventAttendees: {
"eventAbc": {
abc: true,
def: true,
},
"eventDef": {
abc: true,
}
}
}
To get user's events, I have the following which actually works:
getEvents(userId) {
const self = this;
const query = firebase.firestore().collection('eventAttendees');
var promises = [];
query.onSnapshot(function(snap) {
snap.docs.forEach(doc => {
const data = doc.data();
if (data[userId]) {
// user has been invited to this event
promises.push(self.getEvent(doc.id));
}
});
Promise.all(promises).then((results) => {
console.log("All events");
console.log(results);
});
});
}
getEvent(eventId) {
return new Promise((resolve) => {
const query = firebase.firestore()
.collection('events')
.doc(eventId);
query.onSnapshot(function(snap) {
resolve(snap.data());
});
});
}
getEvents('abc');
Questions
I don't think/know if I have the right and optimized approach? Any documentation/Github project I should look into as reference?
What does happen if 'abc' has been invited to 1 million events? I feel, I'm looping and need to handle pagination out of the box. Is there a better way?
Let's assume, each event has a lot of information (details). However, on the homepage, I just need to display main information (event title, event date). Where should I store that to avoid loading a lot of information.
Hopefully, there is someone who can find the time to reply to my questions.
Thanks.
You seem to be watching David's great video series Firebase for SQL developers, which was written for the Firebase Realtime Database. Since your code uses the Cloud Firestore API, I'd recommend switching over to Todd's Getting to know Cloud Firestore series, which contains similar information and much more, but then tailored towards Firestore.
What does happen if 'abc' has been invited to 1 million events?
No user is ever going to watch those million events. So don't load them. Think of how many items a user will realistically see, and then load those. Typically this will be one or a few screenfuls. If you think they may want to load more, allow them to load a new page. Search firebase and pagination to see many questions about that topic.
Let's assume, each event has a lot of information (details). However, on the homepage, I just need to display main information (event title, event date). Where should I store that to avoid loading a lot of information.
You should only load the data you need. If you need a subset of the data for each event in the initial list view, create a separate node or collection with just that information for each event. Yes, you're indeed duplicating data that way, but that is quite normal in NoSQL databases.
Thanks for taking the time to reply to my questions. Indeed, I like this video series and I'm looking forward to watch the one you've recommended which seems to be more recent.
I agree with you and definitely want to load only what's needed. At the moment, I don't have much datas so it's difficult for me to conceive/anticipate that. I should have probably taken advantage of .limit(10) or .startAt() in the function getEvents?
I'm not familiar with database in general and don't really know where I should have a subset of the data for each event? Is the following what you would call initial list view? For my example, is it the right place? If not, where would put that?
eventAttendees: {
"eventAbc": {
title: "Some great event",
eventDate: "",
moreKeyInformation: "",
abc: true,
def: true,
},
"eventDef": {
title: "Another great event",
eventDate: "",
moreKeyInformation: "",
abc: true,
}
}
Related
Let's say I have 2 models.
Post
Author
The Author looks like below:
{
authorId: 'ghr4334t',
fullName: 'This is a post!'
Nickname: 'Avola
}
The Post looks like below and will have a reference to the author like below:
{
postId: '12fdc24',
authorId: 'ghr4334t',
content: 'This is a post!'
}
Currently, when a user clicks on a post, in order to show all the relevant information, I load the data as follow:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
// update state so I have the post object and author object.
})
})
So the above, I load the post, then I load the author. Once I've loaded them both, I can finally construct a custom object:
const finalPost = {
author: { ...this.state.authorData },
post: { ...this.state.postData }
}
Naturally..If I have a couple more fields that reference other collections, there will be a nest of get and .then() calls like below:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
getSomethingElse(listing.uid).then((document) => {
getAnother(listing.uid).then((document) => {
// finally update state with everything.
})
})
})
})
Is there a more a better way to load related information together without having to stack .then() calls?
Unfortunately, there isn't a better way to achieve what you want, with queries directly. Queries in Firestore doesn't provide you with many options on how to query and return data, mainly, when you would need to do any kind of JOIN on them, to search via references, which makes the work not very easy. I believe the way you are doing is the best option you have for more now.
An alternative you can try is to have Subcollections, where you will have a subcollection of Author inside your collection Post. This way, you will only treat with the reference of the Post, since the Author will be within the document of each specific Post. This way, the queries would be more simple, looking like this below. Of course, this would require you to modify your database.
var messageRef = db.collection('Post').doc('Post1')
.collection('Author').doc('Author1');
In case you still think this is not enough, I would recommend you to raise a Feature Request at Google's System, where the Google Developers will be able to check if having a new way of getting data is possible to be implemented.
Let me know if the information clarified your doubts!
I am using Firebase Realtime Database. I have an object which has all the posts created by all our users. This object is huge.
In order to display the posts in a fast way, we have given each user an object with relevant post IDs.
The structure looks like this:
/allPosts/$postID/
: { $postID: {id: $postID, details: '', title: '', timestamp: ''} }
/user/$userID/postsRelevantToThisUser/
: { $postID: {id: $postID} }
'postsRelevantToThisUser' only contains the IDs of the posts. I need to iterate over each of these IDs and retrieve the entire post information from /allPosts/
As a result, the client won't have to download the entire allPosts object and the app will be much faster.
To do this, I've written the below code. It is successfully retrieving and rendering only the relevant posts. Whenever a new postID is added or removed from /postsRelevantToThisUser/ in Firebase Realtime Database, React Native correctly re-renders the list.
However, when anything in /allPosts/$postID changes, for exampe: if title parameter changes, it is not reflected in the view.
What's a good way to solve this problem?
let userPostRef = firebase.database().ref(`/users/${uid}/postsRelevantToThisUser`)
userPostRef.on('value', (snapshot) => {
let relPostIds = [];
let posts = [];
snapshot.forEach(function(childSnapshot) {
const {id} = childSnapshot.val();
relPostIds.push(id);
})
relPostIds.map(postId => {
firebase.database().ref(`allPosts/${postId}`).on('value', (postSnapshot) => {
let post = postSnapshot.val()
posts.push(post);
this.setState({ postsToRender:posts });
})
})
Since you've spread the data that you need to show the posts to the user over multiple places, you will need to keep listeners attached to multiple places if you want to get realtime updates about that data.
So to listen for title updates, you'll need to keep a listener to each /allPosts/$postID that the user can currently see. While it can be a bit finicky in code to keep track of all those listeners, they are actually quite efficient for Firebase itself, so performance should be fine up to a few dozen listeners at least (and it seems unlikely a user will be actively reading more post titles at once).
Alternatively, you can duplicate the information that you want to show in the list view, under each user's /user/$userID/postsRelevantToThisUser nodes. That way you're duplicating more data, but won't need the additional listeners.
Either approach is fine, but I have a personal preference for the latter, as it keeps the code that reads the data (which is the most critical for scalability) simpler.
I building react-native chatroom feature by using Firebase. I would like to sort the chatList according to the lastMessage createdAt value in another collection.
Here is the sample structure of my current structure chat:
{
Chatrooms: {
CHAT_ID_1: {
messages: [...],
metadata: {
lastMessages: {
createdAt: 1515857717832 //the time
},
users: [USER_ID_1, USER_ID_2]
}
},
CHAT_ID_2: {
messages: [...],
metadata: {
lastMessages: {
createdAt: 1515857717834 //the time
},
users: [USER_ID_1, USER_ID_3]
}
}
},
Users: {
USER_ID_1: {
chatList: {
USER_ID_3: CHAT_ID_2,
USER_ID_2: CHAT_ID_1,
}
}
}
}
Since the CHAT_ID_2 has the latest lastMessage createdAt time, it should be the first one in the chatList of USER_ID_1.
I would like to know how to sort this.
From what I see there is no way to do a query that gets you the chat rooms that a specific user is in order by last update timestamp.
Solutions I can quickly think of:
Load the last update timestamp for each room the user is in with a separate call, and then order them client-side.
Store the last update timestamp for each chat room for each user and then do a query.
I hope the first solution is self-explanatory. While it does require some extra code, it's actually a lot more efficient than most developers think because the calls from the client to the database are pipelined over the same connection.
The second solution is to add additional data to /Users/$uid/$chatroomid to suit your use-case. So for example:
Users: {
USER_ID_1: {
chatList: {
CHAT_ID_2: 1515857717834
CHAT_ID_1: 1515857717832
}
}
}
With this structure you can get the chats in order of most recent update with:
firebase.database().ref("Users/USER_ID_1/chatList").orderByValue()
But in this case I'd go for option 1 myself. Since the number of chat rooms for each user is likely to be fairly small and the loads don't require queries, it's likely to both perform and scale well.
I am currently creating an app where the administrator should be able to tag images. The visitors can search for a tag, and see the images that have that tag.
One image can have more than one tag. This represents how I currently have set up my data:
Images: {
PUSH_ID: {
url: "https://cdn.filestackcontent.com/o0ze07FlQjabT9nuteaE",
tags: {
PUSH_ID: {
tag: "1324"
},
PUSH_ID: {
tag: "4321"
}
}
}
}
When a visitor searches for a tag, I need to be able to query the tag, and find the URL of the images that have the given tag. I was thinking that something along the lines of this would work:
ref.orderByChild('tags/tag').equalTo(tag).once("value"...)
But after some reading I have come to the understanding that you can only query one level deep in Firebase.
If this is the case, I need to restructure my data, but I cannot figure out how it should be structured.
Can anyone help me?
Btw; I have been told that I should use something like Elasticsearch to query in Firebase, but this app is for an event with a limited ammount of traffic.
Thanks!
When modeling data in Firebase (and in most NoSQL databases), you need to model the data for how your app wants to use it.
For example:
Images: {
PUSH_ID_1: {
url: "https://cdn.filestackcontent.com/o0ze07FlQjabT9nuteaE",
tags: {
"tag1324": true,
"tag4321": true
}
},
PUSH_ID_2: {
url: "https://cdn.filestackcontent.com/shjkds7e1ydhiu",
tags: {
"tag1324": true,
"tag5678": true
}
}
},
TagsToImages: {
"tag1324": {
PUSH_ID_1: true,
PUSH_ID_2: true
},
"tag4321": {
PUSH_ID_1: true
}
"tag5678": {
PUSH_ID_2: true
}
}
I changed a few things from you model:
I still store the tags for each image, so that you can show them when a user is watching a single image
But now we store the tags in a "tag1234": true format, which prevents a image from being tagged with the same tag multiple times. Whenever you feel the need for an array that you want to do a contains() operation on, consider using this approach which is more akin to a set.
I prefix the tag numbers with a static string, which prevents Firebase from trying to interpret the tag numbers as array indices.
We now also store a map of tags-to-image-ids. In this map you can easily look up all image IDs for a specific tag and then load the images in a loop.
We've essentially duplicated some data (the tags are stored twice) to ensure that we can look the information up in two ways. If you have more ways you want to access the data, you may need to replicate even more. This is normal in NoSQL databases and is part of the reason they scale so well.
I highly recommend reading this article on NoSQL data modeling.
And there is a Tags node in this structure like this, isnt it??
Tags: {
"tag1324": {
tagName: pets
},
"tag4321": {
tagName: daffodil
}
"tag5678": {
tagName: tasmanian wolf
}
I'm trying to build a complex fully-dynamic app with Redux. I mean my App has lots of dynamic-generated forms with generated fields-components on-the-fly. I want to store in my Redux-store visual data about my components too. But how should i do it without mixing real data with visual component data?
For example if i have structure like this
Store {
visual: {...deeply nested visual-data-tree...},
data: {...deeply-nested real-data-tree...}
}
It is hard to render component because i need to search visual data first, then react component "value" in two trees.
But if have a structure similar to this:
Store {
form {
visual: {...form visual data...},
data: {
//Ok here the form "data" - widgets. Or it must to be visual? :)
widget1 {
visual: {type:"ComboBox", opened: true},
data: 1
}
}
}
}
You see the problem, now i have visual data inside real data of Form widget.
(form - data - widget1 - visual)
Visual data inside the real data is out of the concept.
How do you guys solve same problems of mixing data?
Really sorry for my poor english. I hope i clearly explained the problem.
Isn't the distinction superficial? I think a more important rule is that the data in the state should be normalized. For example, if you have Combobox widget letting you choose users, your data shape better be
{
chosenUserId: 10, // Good!
users: {
10: { name: 'Alice' }
}
rather than
{
chosenUser: { name: 'Alice' }, // Bad!
users: {
10: { name: 'Alice' }
}
If the data is duplicated in the state tree, it's hard to update it correctly and avoid inconsistencies.
As long as you keep the data normalized, I see no real need to divide visual and data. You might want to have top-level entity cache that looks like a database (e.g. entities which includes users, posts, or whatever data objects your app uses), but other than that, go with whatever state shape feels most comfortable when retrieving the relevant state.