How to get unique random product in node Firebase?

Data:

- products
   - -L74Pc7oVY22UsCETFBv
       - name: "gjwj"
       - category: "hreggrrg"
       - location: "vjhiwehifwe"
       - price: 44
       - color: fassaf
   - -L74uJ7oVYcVNyCteFBz
       - name: "uygfwh"
       - category: "hhhjwwwom"
       - location: "pervrr"
       - price: 33
       - color: yrtrr
   ......................

I have many products in node products, over 1000 products. I can display all in ListView. I want to display only one unique random to user, like highlights, but not to download all, only one.

Code:

ValueEventListener v = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for(DataSnapshot child : dataSnapshot.getChildren()) {
            String name = (String) child.child("name").getValue().toString();
            Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
            //How to get?????
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {}
};
FirebaseDatabase.getInstance().getReference().addListenerForSingleValueEvent(v);

How to get unique random product in node Firebase?


Solution 1:

Well, there are a few good answers here on SOF but are separated, so I will try to answer your question with two approaches.

But first of all, before writing some code, I can tell you that this code is not working because you are missig a child in your reference, which is products, obviously assuming that the products node is a direct child of your Firebase database root.

The actual answer:

Assuming that your database structure looks like this (in which the products node is a direct child of your Firebase database):

Firebase-root
   |
   --- products
         |
         --- productIdOne
         |      |
         |      --- name: "gjwj"
         |      |
         |      --- category: "hreggrrg"
         |      |
         |      --- location: "vjhiwehifwe"
         |      |
         |      --- price: 44
         |      |
         |      --- color: "fassaf"
         |
         --- productIdTwo
         |      |
         |      --- name: "uygfwh"
         |      |
         |      --- category: "hhhjwwwom"
         |      |
         |      --- location: "pervrr"
         |      |
         |      --- price: 33
         |      |
         |      --- color: "yrtrr"
         |
         --- //And so on

To get a random product, please use the following code:

ListView listView = (ListView) findViewById(R.id.list_view);
ArrayAdapter arrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, randomProductList);
listView.setAdapter(arrayAdapter);
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference productsRef = rootRef.child("products"); //Added call to .child("products")
ValueEventListener valueEventListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        List<String> productList = new ArrayList<>();
        for(DataSnapshot ds : dataSnapshot.getChildren()) {
            String name = ds.child("name").getValue(String.class);
            productList.add(name);
        }

        int productListSize = productList.size();
        List<String> randomProductList = new ArrayList<>();

        randomProductList.add(new Random().nextInt(productListSize)); //Add the random product to list
        arrayAdapter.notifyDatasetChanged();
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.d(TAG, "Error: ", task.getException()); //Don't ignore errors!
    }
};
productsRef.addListenerForSingleValueEvent(valueEventListener);

In order to get all the products, you need to loop through all children of the products node. So a call to child("products") is mandatory.

If you want more then one random product, then you can create a loop and add as many random products as you want in your randomProductList.

This is called the classic solution and you can use it for nodes that contain only a few records but if you are afraid of getting huge amount of data then I'll recommend you this second approach. This also involves a little change in your database by adding a new node named productIds. Your database structure should look like this:

Firebase-root
   |
   --- products
   |     |
   |     --- productIdOne
   |     |      |
   |     |      --- //details
   |     |
   |     --- productIdTwo
   |            |
   |            --- //details
   |      
   --- productIds
          |
          --- productIdOne: true
          |
          --- productIdTwo: true
          |
          --- //And so on

So as you mentioned in your question, if you want to avoid downloading the entire products node which contains all the products with all the properties, you have to create a separate node named productIds. So to get a single product, you'll need only to download a simple node that contains only the product ids.

This practice is called denormalization (duplicating data) and is a common practice when it comes to Firebase. For a better understanding, i recomand you see this video, Denormalization is normal with the Firebase Database.

But rememebr, in the way you are adding the random products in this new created node, in the same way you need to remove them when there are not needed anymore.

So to get the random product, you need to query your database twice. Please see the code below:

DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference productIdsRef = rootRef.child("productIds");
ValueEventListener valueEventListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        List<String> productIdsList = new ArrayList<>();
        for(DataSnapshot ds : dataSnapshot.getChildren()) {
            String productId = ds.getKey();
            productIdsList.add(productId);
        }

        int productListSize = productList.size();
        List<String> randomProductList = new ArrayList<>(););

        DatabaseReference productIdRef = rootRef.child("products").child(productIdsList.get(new Random().nextInt(int productListSize));
        ValueEventListener eventListener = new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                String name = dataSnapshot.child("name").getValue(String.class);
                Log.d("TAG", name);
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.d(TAG, "Error: ", task.getException()); //Don't ignore errors!
            }
        };
        productIdRef.addListenerForSingleValueEvent(eventListener);
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.d(TAG, "Error: ", task.getException()); //Don't ignore errors!
    }
};
productIdsRef.addListenerForSingleValueEvent(valueEventListener);

When you execute a query against the Firebase Database, there will potentially be multiple results. So the dataSnapshot contains a list of those results. Even if there is only a single result, the dataSnapshot object will contain a list of one result.