Asked  6 Months ago    Answers:  5   Viewed   41 times

I am storing data in Firebase storage.

Object Comment with attribute timestamp. When I push data from device to Firebase I'm populating timestamp with currentTime and store in long data type.

When I do retrieving the data with firebaseRef.orderByChild("timestamp").limitToLast(15) result is not sorting how I expected.

I even played around with rules and no result:

{
    "rules": {
        ".read": true,
        ".write": true,
        ".indexOn": "streetrate",
        "streetrate": {
          ".indexOn": ".value"
        }
    }
}

I tried store timestamp in String data type, same issue.

 Answers

91

Firebase can order the items in ascending order by a given property and then returns either the first N items (limitToFirst()) or the last N items (limitToLast()). There is no way to indicate that you want the items in descending order.

There are two options to get the behavior you want:

  1. Use a Firebase query to get the correct data, then re-order it client-side

  2. Add a field that has a descending value to the data

For the latter approach, it is common to have a inverted timestamp.

-1 * new Date().getTime();
Tuesday, June 1, 2021
 
BrunoRamalho
answered 6 Months ago
48

The problem in your code lies in the fact that you have in your CustomListAdapter class a field named ItemName but you are using a getter named getItemName(), which is not correct since Firebase is looking in the database for a field named itemName and not ItemName. See the lowercase i letter vs. capital letter I?

There are two ways in which you can solve this problem. The first one would be to change your model class by renaming the fields according to the Java Naming Conventions. So you model class should look like this:

public class CustomListAdapter {
    private String itemName, quantity, serialNo, supplierName, supplierEmail, supplierPhone;

    public CustomListAdapter() {}

    public CustomListAdapter(String itemName, String quantity, String serialNo, String supplierName, String supplierEmail, String supplierPhone) {
        this.itemName = itemName;
        this.quantity = quantity;
        this.serialNo = serialNo;
        this.supplierName = supplierName;
        this.supplierEmail = supplierEmail;
        this.supplierPhone = supplierPhone;
    }

    public String getItemName() { return itemName; }
    public String getQuantity() { return quantity; }
    public String getSerialNo() { return serialNo; }
    public String getSupplierName() { return supplierName; }
    public String getSupplierEmail() { return supplierEmail; }
    public String getSupplierPhone() { return supplierPhone; }
}

See in this example, there are private fields and public getters. There is also a simpler solution, to set the value directly on public fields like this:

public class CustomListAdapter {
    public String itemName, quantity, serialNo, supplierName, supplierEmail, supplierPhone;
}

Now just remove the current data and add it again using the correct names. This solution will work only if you are in testing phase.

There is also the second approach, which is to use annotations. So if you prefer to use private fields and public getters, you should use the PropertyName annotation only in front of the getter. So your CustomListAdapter class should look like this:

public class CustomListAdapter {
    private String ItemName;
    private String Quantity;
    private String SerialNo;
    private String SupplierName;
    private String SupplierEmail;
    private String SupplierPhone;

    public CustomListAdapter() {}

    public CustomListAdapter(String itemName, String quantity, String serialNo, String supplierName, String supplierEmail, String supplierPhone) {
        ItemName = itemName;
        Quantity = quantity;
        SerialNo = serialNo;
        SupplierName = supplierName;
        SupplierEmail = supplierEmail;
        SupplierPhone = supplierPhone;
    }

    @PropertyName("ItemName")
    public String getItemName() { return ItemName; }
    @PropertyName("Quantity")
    public String getQuantity() { return Quantity; }
    @PropertyName("SerialNo")
    public String getSerialNo() { return SerialNo; }
    @PropertyName("SupplierName")
    public String getSupplierName() { return SupplierName; }
    @PropertyName("SupplierEmail")
    public String getSupplierEmail() { return SupplierEmail; }
    @PropertyName("SupplierPhone")
    public String getSupplierPhone() { return SupplierPhone; }
}
Tuesday, June 1, 2021
 
Dunc
answered 6 Months ago
98

Unfortunately firebase doesn't allow returning by descending order. I have generally just reversed the order of the results returned client side.

If your data query is too large to sort client side you can do

firebase.database().ref('books').orderByChild('ups').limitToLast(2)

and then invert the results from there. Documentation can be seen here

Thursday, July 29, 2021
 
macha
answered 4 Months ago
16

What you're seeing is likely the fact that data from Firebase is loaded asynchronously. The first time you encounter this, it's a big paradigm shift. But since most modern cloud APIs work this way, it's best to embrace it as quickly as possible.

The easiest way I've found to see what happens when we place some logging statements:

System.out.println("Before adding listener");
mRefReg.addValueEventListener(new ValueEventListener() {
    public void onDataChange(DataSnapshot dataSnapshot) {
        System.out.println("In onDataChange");
    }
    public void onCancelled(DatabaseError databaseError) { }
});
System.out.println("After adding listener");

Contrary to what your intuition tells you, the output of this will be:

Before adding listener

After adding listener

In onDataChange

The reason for this is (as I said at the start) that Firebase asynchronously gets the data from the database. A fun way to realize why that is, is to make a change to your data (for example in your Firebase Database console). When you make this change, the onDataChange will be invoked again and print another:

In onDataChange

Unfortunately this means that you cannot do the following to handle the children count:

int count = -1;
System.out.println("Before adding listener, count="+count);
mRefReg.addValueEventListener(new ValueEventListener() {
    public void onDataChange(DataSnapshot dataSnapshot) {
        count = dataSnapshot.getChildrenCount();
        System.out.println("In onDataChange, count="+count);
    }
    public void onCancelled(DatabaseError databaseError) { }
});
System.out.println("After adding listener, count="+count);

Because the output will be:

Before adding listener: count=-1

After adding listener: count=-1

In onDataChange: count=3 (assuming you have 3 children)

The best trick I've found to deal with this asynchronous loading is to reframe the problem. Instead of saying "first we get the count, then we do xyz with it", try framing it as "whenever we get the count, do xyz with it". This means that you need to move the code that requires the count into onDataChange():

mRefReg.addValueEventListener(new ValueEventListener() {
    public void onDataChange(DataSnapshot dataSnapshot) {
        int count = dataSnapshot.getChildrenCount();
        // TODO: show the count in the UI
    }
    public void onCancelled(DatabaseError databaseError) { }
});

If you implement it like this, the count in the UI will be updated whenever the number of items in the database changes.

Friday, August 6, 2021
 
Trav L
answered 4 Months ago
54

you try to mix up the Firebase Admin SDK with the Android SDK. The method createCustomToken is only available at the Admin SDK and not for the Android SDK. The docs for Android can be found here

https://firebase.google.com/docs/auth/android/start/

The Admin SDK can only be used at server side

Thursday, August 26, 2021
 
sophie
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share