I am trying to make a chat app with firebase real time database. I store chats between 2 users as an JSON array in firebase.
Initially I when user used to send message, I used to set whole chat array. But I soon realised that's not a good idea as array will grow.
await firebase.database().ref("chats/" + chatId).set(messages)
When I checked official documentation on how to handle Arrays in firebase, I realised I can use push() instead. I am doing something like this now:
let messageRef = firebase.database().ref("chats/" + chatId)
await messageRef.push().set(message);
It causes 2 problems, one is it generates unique keys and other is when I fetch the chat it returns JSON object instead of Array.
I want something like this:
Instead after using push I am getting:
What's the best way to achieve what I want?
As you can see calling push() will generate a key for you.
Two solutions:
• You let Firebase generate unique key for you and transform the JSON object into an array on client side:
firebase.database().ref("chats/" + chatId).once("value").then(data => {
console.log(Object.values(data.val()));
});
Read more here https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/values
• You use your own keys ("0", "1", "2"...)
firebase.database().ref("chats/" + chatId).child(key).set(msg);
The inconvenience here is you need to know the key. You can use messages.length or store a new field with the size of messages (use transaction to increment it). Don't forget to write rules to prevent users overwriting.
If you can you should use the first solution.
Related
I'm using react and firebase real-time database. I want to create a list for each user. A list of id to later look for files in storage. In this case what is the best method to generate and add an id to list in database? (Not list of data, the data stored in the database is the list)
So I have two methods in mind. I'll write in pseudo code
//Method 1
//get id list
receivedList = getKeyListFromDB()
//generate ID
newID = generateID()
// append new id
receivedList.append(newID)
//set value with updated list
updateDB(receivedList)
//Method 2
// Using the list provided by the firebase
newRef = firebase.database().ref().child('pathToList').push()
newRef.set({
key: newRef.key
})
I was going for method 1, but couldn't find a way to generate id. And retreiving the list to update it seemed inefficient. So I tried to find another method. But I can't find a good way to do this.
Solution to add a value to list in database
I have solved using transaction method. But if there is a way to solve by using push please do answer.
There are many reasons why Firebase has a built-in push() operation to add a child node to a list. In fact, you've already listen one of them in your question:
retreiving the list to update it seemed inefficient
Other reasons are that push IDs will work while your app is offline, and that they scale much better across multiple users.
If you want to learn more about why you should use push(), read Best Practices: Arrays in Firebase.
First, you can generate a unique key by using below code
const newID = firebase.database.ref().push().key
According to this answer, whenever you use push on a Database Reference, a new data node is generated with a unique key that includes the server timestamp. These keys look like -KiGh_31GA20KabpZBfa.
Because of the timestamp, you can be sure that the given key will be unique, without having to check the other keys inside your database.
Second, you can add an item to a list in database using transaction.
const newID = firebase.database.ref().push().key
//user is user object from firebase auth module
const newDatabaseRouteRef = firebase.database.ref().child('users/' + user.uid + '/ids')
// if null, create a new list with id, if not add newID to list
newDatabaseRouteRef.transaction((currentValue) => {
return currentValue ? currentValue.push(newID) : [newID]
})
I'm creating another answer just to show different kinds of solutions. This solution, using push is better than using transaction.
// Get a new ref by push
const newDatabaseRouteRef = firebase.database.ref().child('users/' + user.uid + '/routes').push()
// Set the value to the key of received ref
newDatabaseRouteRef.set(newDatabaseRouteRef.key)
This is better because
Transaction needs multiple round trips where push() doesn't, thus reducing time.
Is more safe according to this article.
This is achieves the same result because even using list, when the list is saved in firebase database, it is stored as object. ex) ["a","b","c"] => {0:"a",1:"b",2:"c"}
Dataloader is able to batch and cache requests, but it can only be used by either calling load(key) or loadMany(keys).
The problem I am having is that sometimes I do not know they keys of the items I want to load in advance.
I am using an sql database and this works fine when the current object has a foreign key from a belongsTo relation with another model.
For example a user that belongs to a group and so has a groupId. To resolve the group you would just call groupLoader.load(groupId).
On the other hand, if I wanted to resolve the users within a group, of which there could be many I would want a query such as
SELECT * from users where user.groupId = theParticularGroupId
but a query such as this doesn't use the keys of the users and so I am not sure how make use of dataloader.
I could do another request to get the keys like
SELECT id from users where user.groupId = theParticularGroupId
and then call loadMany with those keys... But I could have just requested the data directly instead.
I noticed that dataloader has a prime(key, value) function which can be used to prime the cache, however that can only be done once the data is already fetched. At which point many queries would already have been sent, and duplicate data could have been fetched.
Another example would be the following query
query {
groups(limit: 10) {
id
...
users {
id
name
...
}
}
}
I cannot know the keys if I am searching for say the first or last 10 groups. Then once I have these 10 groups. I cannot know the keys of their users, and if each resolver would resolve the users using a query such as
SELECT * from users where user.groupId = theParticularGroupId
that query will be executed 10 times. Once the data is loaded I could now prime the cache, but the 10 requests have already been made.
Is there any way around this issue? Perhaps a different pattern or database structure or maybe dataloader isn't even the right solution.
You'll want a dataloader instance for the lookup you can do, in this case you have a group ID and you want the users:
import DataLoader from 'dataloader';
const userIdsForGroupLoader = new DataLoader(groupIds => batchGetUsersIdsForGroups(groupIds));
Now your batchGetUsersForGroups function is essentially has to convert an array of group IDs to an array of arrays of users (one array of user IDs for each group).
You'd start off with an IN query:
SELECT id from users where user.groupId in (...groupIds)
This will give you a single result set of users, which you'll have to manipulate, by grouping them by their groupId, the array should be ordered according to the original array of groupIds. Make sure you return an empty array for groupIds that don't have any users.
Note that in this we're only returning the user ids, but you can batch fetch the users in one go once you have them. You could tweak it slightly to return the users themselves, you'll have to decide for yourself if that's the right approach.
Everything I mention in this article can be achieved using clever use of Dataloader. But the key takeaway is that the values you pass to the load/loadMany functions don't have to correspond to the IDs of the objects you're trying to return.
I have the following database structure in Firebase:
firebaseDB
Each object contains category and sectionIndex which correspond to a chapter and its sort order.
I am trying to grab those datas in order to display it in my menu.
I can acces to the DB with this code below:
firebase.database().ref().on("value", snapshot => {console.log(snapshot.val());} ,error => {console.log("Error: " + error.code);} );
There is the result in the console:
consoleScreen
My question is how can I enter in each object (0,1,2,3,4,...,45), grab the category value and sort it in order following the sectionIndex and avoid to duplicate the value.
Firebase has a set of queries outlined in the docs which allow you to do some basic manipulation of data. In your case, it looks like you'll want to use orderByChild to sort your entire database object by sectionIndex. You can then specify how many you want to get at one time using the limitToFirst or limitToLast parameters.
Then, when you get your snapshot, just grab all the categoryValues and do what you want with them. Rather than using .on("value") like you are currently, it's probably easier to use .on('child_added') like in their example:
var ref = firebase.database().ref("dinosaurs");
ref.orderByChild("height").on("child_added", function(snapshot) {
console.log(snapshot.key + " was " + snapshot.val().height + " m tall");
});
It shouldn't be to hard for you to apply this to your case.
Remember also that depending on what else you want to do with this data, and depending on how many items your're going to have, it may be easier to do the sorting on the client side (i.e. rather than letting Firebase do the sorting, make your computer/app do the sorting)
my receipe data in firebase
![my receipe data in firebase][1]
My receipe data looks like the following
receipe/1/12: "{"itemno":"1","receipeno":"12","receipedescript..."
How can I filter all receipes where receipeno = 12 or starting with receipeno = 12, irrespective of itemno?
I have tried the following, but got no results or errors?
Also tried startAt
this.query = this.db.database.ref("receipe").orderByChild("receipeno").equalTo("12").limitToFirst(100);
BTW: this.query = this.db.database.ref("receipe") this returns all data
and this.db.database.ref("receipe/1") returns all receipe for itemno == 1.
I have updated the data to not use opaque strings.
[Updated db so as not to use opaque strings][2]
and have tried the following.
this.query = this.db.database.ref("receipe").orderByChild("itemno").equalTo("1").limitToFirst(100);
And
this.query = this.db.database.ref("receipe").orderByChild("receipeno").equalTo("r1").limitToFirst(100);
Firebase is not going to help you with deep queries. It filters and sorts at one level. Therefore, there is no way to say, "find me all the recipes with an id (or recipeno) of 12, under any node under recipes.
If you had a single level, then you would do orderByKey().equalTo("12"). Of course this will limit you to one recipe per ID.
If you are going to have multiple recipes with the number 12 and want to do this kind of querying, you need to change your database structure--essentially getting rid of the intermediate level where you currently have keys of 1, 2 etc. Most likely in this case you would use automatically generated keys of the kind that result from push, such as:
+ recipes
+ autokey1: {itemno, recipeno, recipedescription}
+ autokey2: {itemno, recipeno, recipedescription}
Then you could say
orderByChild('recipeno').equalTo('12')
This assumes, of course, as pointed out in a comment, that you are saving the data as objects, not as stringified JSON, which Firebase will never be able to query against the insides of.
This is a good case study of the notion that you should design your Firebase database structure carefully in advance to allow you to do the kinds of querying you will need to do.
By the way, this question has nothing to do whatsoever with Angular.
I have the following hierarchy on firebase, some data are hidden for confidentiality:
I'm trying to get a list of videos IDs (underlines in red)
I only can get all nodes, then detect their names and store them in an array!
But this causes low performance; because the dataSnapshot from firebase is very big in my case, so I want to avoid retrieving all the nodes' content then loop over them to get IDs, I need to just retrieve the IDs only, i.e. without their nested elements.
Here's my code:
new Firebase("https://PRIVATE_NAME.firebaseio.com/videos/").once(
'value',
function(dataSnapshot){
// dataSnapshot now contains all the videos ids, lines & links
// this causes many performance issues
// Then I need to loop over all elements to extract ids !
var videoIdIndex = 0;
var videoIds = new Array();
dataSnapshot.forEach(
function(childSnapshot) {
videoIds[videoIdIndex++] = childSnapshot.name();
}
);
}
);
How may I retrieve only IDs to avoid lot of data transfer and to avoid looping over retrived data to get IDs ? is there a way to just retrive these IDs directly ?
UPDATE: There is now a shallow command in the REST API that will fetch just the keys for a path. This has not been added to the SDKs yet.
In Firebase, you can't obtain a list of node names without retrieving the data underneath. Not yet anyways. The performance problems can be addressed with normalization.
Essentially, your goal is to split data into consumable chunks. Store your list of video keys, possible with a couple meta fields like title, etc, in one path, and store the bulk content somewhere else. For example:
/video_meta/id/link, title, ...
/video_lines/id/...
To learn more about denormalizing, check out this article: https://www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html
It is a bit old, and you probably already know, but in case someone else comes along. You can do this using REST api call, you only need to set the parameter shallow=true
here is the documentation