How to get sorted data from Firebase Database? - java

How I can get TOP three users(maybe in HashMap) with name and userScore from all users in Firebase Database? HashMap must be order by userScore. Or get ArrayList<User> usersList with three sorted users by userScore.
database
{"top_clicker_db" : {
"total_clicks" : 3401,
"user" : {
"-KkePRVzO_lLLgp7fqX0" : {
"name" : "fly",
"userScore" : 2000
},
"-KkjWk0L2lR8RwUXF2Bd" : {
"name" : "lg",
"userScore" : 24
},
"-KkjjxNw8fj_XG9uUPEg" : {
"name" : "fjfg",
"userScore" : 304
},
"-KkjoakdLIjYlBEM1pkq" : {
"name" : "Geny",
"userScore" : 100
},
"-KklVAOkk2WJaRmH9cKk" : {
"name" : "fly",
"userScore" : 941
}
}
}
}
I need:
fly=2000;
fly=941;
fjfg=304;

To solve the problem in easiest way possible i recomand you change you database structure like this:
{"top_clicker_db" : {
"total_clicks" : 3401,
"user" : {
"-KkePRVzO_lLLgp7fqX0" : {
"fly" : 2000
},
"-KkjWk0L2lR8RwUXF2Bd" : {
"lg" : 24
},
"-KkjjxNw8fj_XG9uUPEg" : {
"fjfg" : 304
},
"-KkjoakdLIjYlBEM1pkq" : {
"Geny" : 100
},
"-KklVAOkk2WJaRmH9cKk" : {
"fly" : 941
}
}
}
}
You need a single child with the user name as the key and the score as the value. The benefits of this structure is that you need to query just once. To achieve this, please use the following code:
DatabaseReference yourRef = FirebaseDatabase.getInstance().getReference().child("top_clicker_db").child("user").child(userId);
Query query = yourRef.child(userName).orderByValue().limitToFirst(3);
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d("TAG", dataSnapshot.getKey() + "=" + dataSnapshot.getValue());
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
query.addListenerForSingleValueEvent(eventListener);
Hope it helps.

You said you cannot change your database structure in the comment in #alex-mamo 's answer, but how about adding a new node?
If you can, denormalize your data may also be a good solution.
https://firebase.google.com/docs/database/web/structure-data
"total_clicks" : 3401,
"user" : {
"-KkePRVzO_lLLgp7fqX0" : {
"name" : "fly",
"userScore" : 2000
},
"-KkjWk0L2lR8RwUXF2Bd" : {
"name" : "lg",
"userScore" : 24
...
},
"scores" : {
"-KkePRVzO_lLLgp7fqX0" : {
"fly" : 2000
},
"-KkjWk0L2lR8RwUXF2Bd" : {
"lg" : 24
},
...
}

I solved the problem. But now I think how I can limited my list only for 3 users.
userRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
getTopThreeUsers((Map<String, Object>) dataSnapshot.getValue());
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
private void getTopThreeUsers(Map<String, Object> users) {
HashMap<String, Long> namesAndScores = new HashMap<>();
for (Map.Entry<String, Object> entry : users.entrySet()) {
Map singleUser = (Map) entry.getValue();
namesAndScores.put((String) singleUser.get("name"), (Long) singleUser.get("userScore"));
}
Set<Map.Entry<String, Long>> set = namesAndScores.entrySet();
List<Map.Entry<String, Long>> list = new ArrayList<>(set);
Collections.sort(list, new Comparator<Map.Entry<String, Long>>() {
public int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
return (o2.getValue()).compareTo(o1.getValue());
}
});
topName1.setText(list.get(0).getKey());
topName2.setText(list.get(1).getKey());
topName3.setText(list.get(2).getKey());
topScore1.setText(String.valueOf(list.get(0).getValue()));
topScore2.setText(String.valueOf(list.get(1).getValue()));
topScore3.setText(String.valueOf(list.get(2).getValue()));
}

Related

Read data from Firebase database when I have multiple unique ids

I'm having trouble writing in my database because I have multiple unique ids that I don't know how to access.
I know that for writing I need to use something like this:
DatabaseReference ref=database.getReference().child("Children")
ChildData data = new ChildData(fullName,age);
ref.push().setValue(data);
My database looks like this:
{
"Children" : {
"55hxObZjRLY9PSZxZxGgSTRKzpc2" : {
"-MzUih5e40OsWPF_Dj0q" : {
"age" : "22",
"fullName" : "Cristina "
},
"plays" : {
"-MznnNih5fItYf2usXB4" : {
"centiseconds" : "70",
"currentDate" : "01.04.2022",
"currentHour" : "04:23:30",
"numberOfFails" : "5",
"seconds" : "2"
}
}
}
},
"Data" : {
"id1" : {
"centiseconds" : "70",
"currentDate" : "01.04.2022",
"currentHour" : "04:23:30",
"numberOfFails" : "5",
"seconds" : "2"
}
}
}
I need the "plays" child to be under "fullName" in "-MzUih5e40OsWPF_Dj0q", not as a separated child. How can I access the path without hardcoding it like this ref.child("MzUih5e40OsWPF_Dj0q").child("plays").push().setValue(data)? I will have multiple children in "Children" with different ids, I won't be able to harcode them.
Is there any function that returns the path for the unique id?
Here is the entire function:
public void writeNewData
(String fullName,String age) {
DatabaseReference reference = database.getReference().child("Data");
DatabaseReference ref=myRef.push();
ChildData data = new ChildData(fullName,age);
ref.push().setValue(data);
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot ds : snapshot.getChildren()) {
DatabaseReference playRef=ref.child("plays");
DatabaseData databaseData = ds.getValue(DatabaseData.class);
playRef.push().setValue(databaseData);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
Edit: This is my desired schema:
{
"Children" : {
"55hxObZjRLY9PSZxZxGgSTRKzpc2" : {
"-MzUih5e40OsWPF_Dj0q" : {
"age" : "22",
"fullName" : "Cristina ",
"plays" : {
"-MznnNih5fItYf2usXB4" : {
"centiseconds" : "70",
"currentDate" : "01.04.2022",
"currentHour" : "04:23:30",
"numberOfFails" : "5",
"seconds" : "2"
}
}
},
}
},
"Data" : {
"id1" : {
"centiseconds" : "70",
"currentDate" : "01.04.2022",
"currentHour" : "04:23:30",
"numberOfFails" : "5",
"seconds" : "2"
}
}
}
If you want the plays information to be a sibling of the age and fullName properties, then you will need to have a plays field/property in your ChildData class.
You'll need two classes, one to represent a "play" and one to represent a "child". At its minimum these will look like:
public class Play {
public String centiseconds, currentDate, currentHour, numberOfFails, seconds;
}
public class Child {
public String age, fullName;
public Map<String, Play> plays = new HashMap<>();
}
If you read/write this class, you will get plays nested under the child's data.

Trying to retrieve data from firebase where the category is equal to the string

I am trying to retrieve data from the Firebase Realtime Database and I only want where the category is equal to a String. For example, if my string is football then I should get all the data that has the category football in it. I run my app with the code I am about to show you but I get nothing but a blank page. I don't know what I am doing wrong.
databaseReference = FirebaseDatabase.getInstance().getReference();
if (Categories.InternetConnection.checkConnection(Categories.this)) {
databaseReference.child("Sports").orderByChild("category").equalTo("football")).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
myUploads.clear();
for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
for (DataSnapshot postsnapshot : userSnapshot.getChildren()) {
Model_Information upload = postsnapshot.getValue(Model_Information.class);
Collections.shuffle(myUploads);
myUploads.add(upload);
recyclerView.invalidate();
}
}
linearLayoutWithoutItems.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
aAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(Categories.this, databaseError.getMessage(), Toast.LENGTH_LONG).show();
}
});
Firebase Json
{
"MyStudents" : {
"KihlyfLkJMQ5uZWBDgLWNuBHKAE2" : {
"-MSr8HG6QCR2sO8MZHoM" : {
"category" : "Football",
"created" : "2-19-2021",
"name" : "Benny",
"time" : "2:47 AM",
"timestamp" : 1613684875762
},
"-MSr8awtvzrmm3P6A2LB" : {
"category" : "Basketball",
"created" : "2-19-2021",
"name" : "patrick",
"time" : "2:49 AM",
"timestamp" : 1613684960454,
},
"-MSr8mSn5OSTu5vdT4Wt" : {
"category" : "Football",
"created" : "2-19-2021",
"name" : "Shawn",
"time" : "2:50 AM",
"timestamp" : 1613685007616,
}
{
"MyStudents" : {
"WahlyfLkJMQ5uZWBDgLWNuBHKAE2" : {
"-MSr8HG6QCR2sO8MZHoM" : {
"category" : "Football",
"created" : "2-19-2021",
"name" : "Len",
"time" : "2:47 AM",
"timestamp" : 1613684875762
},
"JJr8awtvzrmm3P6A2LB" : {
"category" : "Basketball",
"created" : "2-19-2021",
"name" : "Armstrong",
"time" : "2:49 AM",
"timestamp" : 1613684960454,
},
"-JJr8mSn5OSTu5vdT4Wt" : {
"category" : "Football",
"created" : "2-19-2021",
"name" : "Bill",
"time" : "2:50 AM",
"timestamp" : 1613685007616,
}
The problem in your code lies in the fact that you are using an incorrect query:
databaseReference.child("Sports").orderByChild("category").equalTo("football"))
Assuming that MyStudents is a child right under your root node and the following statement:
if (Categories.InternetConnection.checkConnection(Categories.this)) { /* ... */ }
Returns true, please use the following query:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference uidRef = rootRef.child(uid);
Query queryByCategory = uidRef.orderByChild("category").equalTo("Football");
queryByCategory.addValueEventListener(/* ... */);
Things to notice:
KihlyfLkJMQ5uZWBDgLWNuBHKAE2 is the UID of the logged-in user and should be added as a child in the reference.
The value of the category property is called "Football" and not "football", with lower-case "f".
Edit:
According to your last comment:
I'm not trying to just get their information I'm trying to get everyone that has students whose category is Football
There is no way you can achieve that using your actual database structure. The most simple solution I can think of is to create a new node that contains all Model_Information objects, no matter the user is. This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database.
Also, when you are duplicating data, there is one thing that needs to keep in mind. In the same way, you are adding data, you need to maintain it. In other words, if you want to update/delete an item, you need to do it in every place that it exists.
Here is the answer. Hopefully its helpful for someone :)
databaseReference = FirebaseDatabase.getInstance().getReference().child("MyStudents");
if (Categories.InternetConnection.checkConnection(Categories.this)) {
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
myUploads.clear();
for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
for (DataSnapshot postsnapshot : userSnapshot.getChildren()) {
Model_Information upload = postsnapshot.getValue(Model_Information.class);
Collections.shuffle(myUploads);
if (upload.getCategory().equals("Football")) {
myUploads.add(upload);
}else{
Toast.makeText(Categories.this, "No results", Toast.LENGTH_LONG).show();
}
recyclerView.invalidate();
}
myUploads.add(upload);
recyclerView.invalidate();
}
}
aAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(Categories.this, databaseError.getMessage(), Toast.LENGTH_LONG).show();
}
});

orderByChild query not working on addListenerForSingleValueEvent Firebase

Im trying to order my database by child value "Month" but orderByChild is not working. It works when I use onChildEventListener instead of addListenerForSingleValueEvent But I dont wnat to use childEventListener
Here is my database
"Stats" : {
"2019" : {
"YxdZZHGy3rW4xdjORfk2i5mFRYG2" : {
"August" : {
"Weight" : {
"Body_Fat" : "29",
"Month" : 8,
"Weight" : "68"
}
},
"October" : {
"Weight" : {
"Body_Fat" : "29",
"Month" : 10,
"Weight" : "67"
}
},
"September" : {
"Weight" : {
"Body_Fat" : "28.5",
"Month" : 9,
"Weight" : "65.5"
}
}
}
}
},
And here is the java code
userStatsDatabase.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Query query = userStatsDatabase.child(ds.getKey()).child("Weight").orderByChild("Month").limitToFirst(12);
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
pastMonthsBodyFat = Float.parseFloat(map.get("Body_Fat").toString());
pastMonthsWeight = Float.parseFloat(map.get("Weight").toString());
pastWeightList.add(pastMonthsWeight);
pastBodyFatList.add(pastMonthsBodyFat);
Toast.makeText(Stats.this, map.get("Month").toString(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
The Toast should print month values in order 8,9,10 but it is printing the values 8,10,9
If userStatsDatabase points to this JSON (for a specific user):
{
"August" : {
"Weight" : {
"Body_Fat" : "29",
"Month" : 8,
"Weight" : "68"
}
},
"October" : {
"Weight" : {
"Body_Fat" : "29",
"Month" : 10,
"Weight" : "67"
}
},
"September" : {
"Weight" : {
"Body_Fat" : "28.5",
"Month" : 9,
"Weight" : "65.5"
}
}
}
Then you can get the results in order of Month with:
Query query = userStatsDatabase..orderByChild("Weight/Month");
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot monthSnapshot: dataSnapshot.getChildren()) {
System.out.println(monthSnapshot.getKey()); // August, September, October
System.out.println(monthSnapshot.child("Weight/Month").getValue(Long.class)); // 8, 9, 10
}
}
...
I gave up using Firebase queries to sort the data , Im sure there is nothing wrong with my code,anyways I decided to sort it "after" adding the data to the arrayList using Collections.sort and it worked. Thanks
P.S if someone knows why my firebase query didnt work I would still like to know the reason :S
Here is the code in case someone else needs to sort data from an arrayList
//sort the item to show months in ascending order
Collections.sort(items, new Comparator<PastMonthsWeightStats>(){
#Override
public int compare(PastMonthsWeightStats obj1, PastMonthsWeightStats obj2) {
// ## Ascending order
// return obj1.firstName.compareToIgnoreCase(obj2.firstName); // To compare string values
//return Float.valueOf(obj1.getMonthNumber()).compareTo(Float.valueOf(obj2.getMonthNumber())); // To compare integer values
// ## Descending order
// return obj2.firstName.compareToIgnoreCase(obj1.firstName); // To compare string values
return Integer.valueOf(obj2.getMonthNumber()).compareTo(Integer.valueOf(obj1.getMonthNumber())); // To compare integer values
}
});

Retrieving multiple data from firebase returning null

I'm using a Firebase database to store and read a list of "clubs" in an Android app.
Below there's the Json representation of that.
{
"clubs" : [ {
"id" : 1,
"image" : {
"allocationByteCount" : 589824,
"byteCount" : 589824,
"config" : "ARGB_8888",
"density" : 320,
"generationId" : 212,
"height" : 384,
"mutable" : false,
"premultiplied" : true,
"recycled" : false,
"rowBytes" : 1536,
"width" : 384
},
"name" : "Axis"
}, {
"id" : 2,
"image" : {
"allocationByteCount" : 589824,
"byteCount" : 589824,
"config" : "ARGB_8888",
"density" : 320,
"generationId" : 214,
"height" : 384,
"mutable" : false,
"premultiplied" : true,
"recycled" : false,
"rowBytes" : 1536,
"width" : 384
},
"name" : "Allies"
}, {
"id" : 3,
"image" : {
"allocationByteCount" : 589824,
"byteCount" : 589824,
"config" : "ARGB_8888",
"density" : 320,
"generationId" : 216,
"height" : 384,
"mutable" : false,
"premultiplied" : true,
"recycled" : false,
"rowBytes" : 1536,
"width" : 384
},
"name" : "Neutrals"
} ]
}
I'm using a FirebaseAdapter class to call methods upon the Firebase database.
But it returns null when trying to get all clubs.
Bellow theres the method that calls it:
public List<BDClub> getClubs() {
list.clear();
database.getReference("clubs").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
System.out.println(dataSnapshot.getChildrenCount());
for(DataSnapshot array : dataSnapshot.getChildren()){
clubs = array.getValue(BDClub[].class);
for(BDClub c : clubs){
list.add(c);
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
return list;
}
Is there a way to retrieve a list of clubs?
Thanks in advance.
Looking at your Json representation i think clubs is child in main database reference so you can try the following:
FirebaseDatabase.getInstance().getReference().child("clubs").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
System.out.println(dataSnapshot.getChildrenCount());
for(DataSnapshot array : dataSnapshot.getChildren()){
clubs = array.getValue(BDClub[].class);
for(BDClub c : clubs){
list.add(c);
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
Tip:
Do not return list like that from the function because these calls happen asynchronously so there is a huge chance that you will get empty list every time,Use listeners:
How to create our own Listener interface in android?

Check if user exists in Firebase database across multiple children nodes

I've a database like this
"Node" : {
"Location_1" : {
"ID1" : {
"address" : "address1",
"balance" : "20",
"batch" : "xyz",
.
.
.
"studentID" : "ID1",
"studentName" : "Username",
"total" : "amount"
}
},
"Location_2" : {
"ID2" : {
"address" : "address2",
"balance" : "0",
"batch" : "xyz",
.
.
"studentID" : "ID2",
"studentName" : "username",
"total" : "amount"
}
}
"Location_3" : {
"ID3" : {
"address" : "address3",
"balance" : "0",
"batch" : "xyz",
.
.
"studentID" : "ID3",
"studentName" : "username",
"total" : "amount"
}
}
I'm able to check if the ID1 exists in a particular Location_1 using the code given below, but I'm unable to find if the ID1 exists across different Location_X id's.
String url = "https://firebaseURL/Node/";
url = url + Location_1;
databaseReference = FirebaseDatabase.getInstance().getReferenceFromUrl(url);
databaseReference.child(ID1).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
Toast.makeText(getApplicationContext(), "ID already present. Enter unique ID!", Toast.LENGTH_SHORT).show();
}
else { // not present, so go ahead and add
}
I tried using a function like this to check if the user is present in all the subNodes.
private boolean checkIfUserExists(String ID) {
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("Location_1");
.
.
String url = "https://firebaseURL/Node/";
databaseReference = FirebaseDatabase.getInstance().getReferenceFromUrl(url);
for(String s: arrayList){
if(getUserExists())
break;
databaseReference.child(s).child(ID).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
setUserExists(true);
}
}
But always the function getUserExists return false. So I end up adding a new user in different location or updating the user's info in the same location.
Please let me know how to proceed on checking the existence of the userID in all the locationIDs

Categories