Searching a firebase database - java

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

Related

Using an unspecified index. Your data will be downloaded

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.

In Android (Java) get all reviews from documents which contain the current user's email address

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.

How to show items from two children in one RecyclerView from Firebase

Below is image representation of my firebase where user upload items under Electronics or Books category and also more categories. I want to build an activity with RecyclerView where I want to show the only item uploaded by a user. Since I'm using userid which is unique to push the details of the item inside a child, I can use userid to retrieve the items from child and display in firebase. But, how to search in each child from different category and show it in one RecyclerView.
My Code to Show the items from Electronics child is
dbreference = FirebaseDatabase.getInstance().getReference("Electronic").child("userid");
dbreference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
for (DataSnapshot data : snapshot.getChildren()) {
final Solditem b1 = data.getValue(SoldItem.class);
child_count++;
list.add(b1);
staggeredBooksAdapter.notifyDataSetChanged();
}
}
}
But I also would like to show the uploaded item by a user which equals to userid as key from Books in the same RecyclerView.
To delete an item from any category I use
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
Query sellerquery = ref.child("Electronic").child("userid");
sellerquery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot appleSnapshot: dataSnapshot.getChildren()) {
appleSnapshot.getRef().removeValue();
}
}
}
To delete all the records of a user-uploaded item which key-value equals to userid, I need to repeat above code.
Is there any best and short method to delete all the records from any category which key-value equals to userid.
Thanks in advance.
While structuring your data in Firebase, you really need to think about the structure of your data very carefully so that you can run efficient queries on them. For example, in your case, you should have structured your data as follows.
userid
- Electronics
- Camera
- Cooker
- Books
- A brief history of time
- Inferno
Hence you could run your queries in such a way that, you could have all the items under a certain userid at once.
If you really need the structure that you have now for other sets of queries that you are planning to do in your application, think about replicating your data as well. That is, you might consider having duplicate entries of your data (replication) in different structure so that you can perform efficient queries. For example, the current structure of your data is suitable for the following query.
Get all userid under a specific category.
I think you have got my point. If you do not want to change the structure of your data then I am afraid that you might have to get all the data in your application from firebase first. Then you have to loop through the data yourself to find the elements that you actually needed.
Hope that helps!

Java: Sort child based on data in Firebase Database

Image here. Data does not sort accordingly like I wanted (O+, O+, A)
Currently, I have data that stored exactly like in the picture above, I want to make all the blood groups to be placed accordingly. For example, It will have only 0+ until the lists end. It will continue with different blood groups such A+, A- etc.
Can firebase actually sort that data? I was looking around, but could not find anything
Inside the Firebase Console, all the nodes are sorted lexicographically by key and this can't be changed.
However, when you retrieve the data on the client, you can choose to sort by one of the child properties. This way they will be sorted by the time they are shown to the client. For example:
// the reference to where your people are stored
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
// create the query
Query peopleBloodTypeQuery = mDatabase.child("people").orderByChild("bloodgroup");
// query the people
peopleBloodTypeQuery.addChildEventListener(new ChildEventListener() {
// add a child added listener
#Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
// do whatever here - I just print the key of each added child
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
}
});

Filtering by grandchild property in firebase [in android]

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);

Categories