Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding '".indexOn": "name"' at authors to your security and Firebase Database rules for better performance
what does it mean?
is there something wrong with the database?
Query query = FirebaseDatabase.getInstance().getReference("authors").orderByChild("name").equalTo(str);
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds: dataSnapshot.getChildren()) {
search_authors author = ds.getValue(search_authors.class);
Log.println(Log.DEBUG, "Test", author.name);
}
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
throw error.toException();
}
});
The warning you see is not related to your client-side code, but an optimization that you should make on the server-side (in this case, for the Firebase Realtime Database).
In your code, you use getReference("authors").orderByChild("name").equalTo(str), which says "for all data under '/authors', find the authors with 'name' equal to the given string".
When your client SDK makes this query to the server, it sends back everything under /authors (if permissions allow it) and says "hey, I don't have an index for 'name', can you deal with it on your end?". The SDK then sifts through the data sent back and pulls out all the documents where 'name' equals the string you gave. It also displays the warning you saw.
During development, this is fine as you are dealing with small amounts of data, but when /authors starts to contain hundreds of entries, you end up downloading all of them when you may only need just one of them - this is inefficient. Before deploying your code to the masses, you should add ".indexOn": "name" to your security rules in the Firebase Console to build the index needed for this query. How to do this is documented here.
As an example, you could use these rules:
{
"rules": {
"authors": {
".read": "auth != null", // any logged in user can read anything under /authors
".indexOn": ["name"], // index all authors by the 'name' field
"$authorId": {
// only the owner can write to their own data
".write": "auth != null && auth.uid == $authorId",
}
}
}
}
After you create such an index, the server can send back only the matching data rather than all of it and the warning stop appearing. Then repeat this for any other fields you plan to query, such as "likeCount", "commentCount", "createdAt", "lastPostAt", and so on as needed.
Related
In my collection of eateries, each eatery has a unique random ID. Within each eatery document there may be a field named 'reviews'. Each review is a map and is stored under a user's email address. Is there any way I can query the database as a whole and get all the reviews for one user email address? I've tried several things, such as storing the user email as a field within the map and using dot notation to try and pull out the review, but to no avail. The best I've come up with so far:
db.collection("eateries")
.whereEqualTo("reviews" , userEmail)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
Log.d(TAG, "restoreReviews onComplete: task.getResult(): " + task.getResult().size());
if (task.isSuccessful()) {
Log.d(TAG, "restoreReviews onComplete: task was successful");
for (QueryDocumentSnapshot document : task.getResult()) {
}
} else {
Log.d(TAG, "restoreReviews Error getting documents: ", task.getException());
}
}
});
Just to reiterate, reviews contains maps of review objects (which are in turn also maps):
A user could leave reviews for lots of eateries (in other documents) and I'd like to retrieve them all. eateriesTest is just a dummy collection to show the types of documents I'm querying, without displaying users' actual email addresses. Any pointers would be greatly appreciated.
The structure of your database does not allow for the kind of query you're trying to do. Firestore can't query for the existence of keys within a map field. It can only query on the values of map keys.
You will have to add the email addresses to the document in a way that can actually be queried. For example, they could be stored as individual elements of an array type field, and filtered with an array-contains query. It's common to duplicate information like this in order to satisfy your queries.
For example, if you have an array field "reviewers" with only the strings of each email, you could filter them like this:
.whereArrayContains("reviewers" , userEmail)
Because you're trying to query an array of reviews the whereEqualTo method doesnt do what youre looking for here. If you replace it with whereArrayContains() method that should fix your problem.
Preface:
For this question to make sense, I need to describe my intent for the database. It will be used to store user data based on a grid of latitude and longitude sectors created by finding the floor value of the exact coordinates of the user when they sign up or log in. The reduced coordinate values are then concatenated, saved in shared preferences on the user's phone, and then set as the primary nodes in the database. This allows searching through the database to be restricted to just the sector the user is currently in which is required by a feature of the app. See the example the nodal layout of the database below:
The Problem:
This system works great, except for that when the user logs in on a new device (or if the shared preferences are lost) from outside the original sector, or from the previous sector, there is no way to know the previous or original sector value.
The Answer:
The answer I came up with is to search every single sector node in the database for the unique uid easily acquired by the following code:
FirebaseAuth mAuth = FirebaseAuth.getInstance()
String currentUser = mAuth.getCurrentUser().getUid();
However, I don't know how to search through every single node (of which there may be thousands because there are thousands of potential sectors) in the database for a particular child. Note that every primary node has a different name, but they all contain the child "users" which can then hold any number of child "uids."
Once the current uid is found in the previous sector-node, my intent is to transfer all of the children of the current uid in the previous sector to the new one. I want to avoid iterating locally through the entire database for the uid as well.
private DatabaseReference userRef = FirebaseDatabase.getInstance().getReference();
mLogin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final String email = mEmail.getText().toString();
final String password = mPassword.getText().toString();
final String currentUserId = mAuth.getUid();
mAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(LoginActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (!task.isSuccessful()) {
Toast.makeText(LoginActivity.this, "sign in error", Toast.LENGTH_SHORT);
} else {
if (task.isSuccessful()){
userRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
})
}
}
}
});
}
});
All this needs to occur after the success of a sign-in event as shown above. I have a feeling I need to use a dataSnapshot for this but I am unsure. I also read another post and considered the following to be the start of something that might work, but I don't know:
Query searchQuery = userRef.child("Users").equalTo(currentUserId);
The flow of the query should be this: Search first node -> Navigate to Users --> iterate through for currentUid --> If currentUid is not found Search the second node... and so on and so forth until the currentUid is found.
If anyone has the answer or any suggestions, guidance, or hints, I would be very grateful. Thank you.
A Firebase Database query can only contain one unknown level in the path. Your current structure doesn't allow you to search all sectors for a specific user. You can search across all users in a single section, but not across sectors.
So while your current code makes it easy to find all users in a specific sector, or even range of sectors, it doesn't make it easy to find the sector for a specific user. To allow the latter use-case you'll need to add an additional data structure for that purpose. Typically something like:
"user-locations": {
"$uid": "
"sector": "37-123",
"location": "..."
}
}
So this means that when a user moves, you'll need to update their location in two places.
This sort of data duplication is common in NoSQL databases, where you need to update your data model to allow the use-cases. It is also quite common to have more complex write operations, in order to make read operations simpler and more scalable.
For more on this, see my answers to:
Firebase Query Double Nested
Firebase query if child of child contains a value
And:
NoSQL data modeling
Firebase for SQL developers
Now, I am trying to find specific user id by search by "phoneNumber" but his phoneNumber in specific node, so it's hard to reach or I don't know how I can find it. I want to get snapshot of this Info node.
Database structure
I am tried to search by query like that:
Query q=FirebaseDatabase.getInstance().getReference("Users")
.orderByChild("phoneNumber").equalTo("8#gmail.com");
```
and also tried this one
```
Query q=FirebaseDatabase.getInstance().getReference("Info")
.orderByChild("phoneNumber").equalTo("8#gmail.com");
But not worked also :(
None of those queries will work since in your first query you are missing the Info child and in the second you are missing the Users child. To solve this, please use the following query:
Query q = FirebaseDatabase.getInstance().getReference()
.child("Users")
.orderByChild("Info/phoneNumber")
.equalTo("8#gmail.com");
First things you need to do you have to make index in your firbase console rules
like this
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read" : true,
".write" : false,
"admin": {
".indexOn": "phone"
}
}
}
then write in you file where you are trying to get data by phone number
Query q=FirebaseDatabase.getInstance().getReference("Users")
.orderByChild("phoneNumber").equalTo("phoneValue")->getValue();
I am using Firebase database with a Json structure to manage users' comments.
{
"post-comments" : {
"post-id-1" : {
"comment-id-11" : {
"author" : "user1",
"text" : "Hello world",
"uid" : "user-id-2"
},....
}
I would like to pull all the comments but excluding the current user's one.
In SQL this will be translated into:
Select * from post-comments where id !="user-id-2"
I understand that Firebase database does not offer a way to excludes nodes based on the presence of a value (ie: user id != ...).
Thus is there any alternative solutions to tackle this. Either by changing the Database structure, of maybe by processing the datasource once the data are loaded.
For the latter I am using a FirebaseTableViewDataSource. is there a way to filter the data after the query?
Thanks a lot
The first solution is to load the comments via .ChildAdded and ignore the ones with the current user_id
let commentsRef = self.myRootRef.childByAppendingPath("comments")
commentsRef.observeEventType(.ChildAdded, withBlock: { snapshot in
let uid = snapshot.value["uid"] as! String
if uid != current_uid {
//do stuff
}
})
You could expand on this and load everything by .Value and iterate over the children in code as well. That method will depend on how many nodes you are loading - .ChildAdded will be lower memory usage.
CONTEXT :
Hi, I'm currently working on an Android project backed by Firebase.
I have set up a denormalized data structure that relates polls to users (many-to-many relationship) via way of votes. Here is an image displaying the votes path of my database. The structure is as follows :
votes -> [pollUid] -> [votePushuid] -> vote object
So in this example we have a single poll that has 4 votes.
I want to run a check to see if a user has already voted on a poll. In order to do this I fetch the pollsUid, then run through its votes to see if any of them contain the voterUid property == to their user uid.
This is done as follows :
FirebaseHandler.getInstance().getMainDatabaseRef()
.child(FirebaseConstants.VOTES) //votes root
.child(pollKey) //polluid
.orderByChild("voterUid")
.equalTo(FirebaseHandler.getInstance().getUserUid())
.limitToFirst(1)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(!dataSnapshot.exists()) {
If the datasnaptshot exists then we know that the user has already voted on this poll and can handle it in the Java logic.
PROBLEM :
The datasnapshot received by onDataChange is always null (ie does not exist) when searching for a specific user's vote on a specific poll. I know for a fact that the vote exists in the db through inspecting the data, and that the userUid is correct via debugging. Removing the equalTo and limitToFirst returns all of the votes for the poll without a problem so clearly the stem of the ref is correct. This implies to me that the issue is created by one of the two methods just mentioned. Even stranger is the fact that this approach does work at certain times, but not at others.
QUESTION :
How do I return a list of firebase stored objects filtered by a grandchild property? If this is not possible what would be the more appropriate datastructure for this problem?
On a further note I've seen people taking the approach of using Query instead of Databasereferences. Perhaps this might have something to do with the current issue.
Your query is correct. I have no problem running that query using my own DB. It's probably the userId doesn't match. DatabaseReference extends Query, that's why you can access Query's methods.
A database structure alternative would be
{ "users_votes": {
"<userId>": {
"<pollId1>" : true,
"<pollId2>" : true,
"<pollId3>" : true
}
}
}
Set the value to that node once the user voted to a poll.
To check if the user has voted for a poll
usersVotesRef.child(FirebaseHandler.getInstance().getUserUid())
.child(pollKey).addListenerForSingleValueEvent(valueEventListener);