Asked  7 Months ago    Answers:  5   Viewed   115 times

What is the best way to display data from an existing Firestore database in a RecyclerView using Android?

This isn't covered as a full explanation in an answer, so I've added this Q&A-style so it can be linked to in comments.

 Answers

19

Assuming you have a Firestore database structure that looks like this:

Firestore-root
    |
    --- products (collection)
           |
           --- documentIdOne (document)
           |        |
           |        --- productName: "Milk"
           |
           --- documentIdTwo (document)
           |        |
           |        --- productName: "Soy Milk"
           |
           --- documentIdThree (document)
                    |
                    --- productName: "Bacon"

A model class that looks also like this:

public class ProductModel {
    private String productName;

    public ProductModel() {}

    public ProductModel(String productName) {this.productName = productName;}

    public String getProductName() {return productName;}
}

And a .XML file that contains a RecyclerView which also looks like this:

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"/>

To display all the product names, please follow the next steps.

First, you need to find the RecyclerView in your activity and set the LinearLayoutManager like this:

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

Then you need to create the root reference of your Firestore database and a Query object like this:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query query = rootRef.collection("products")
        .orderBy("productName", Query.Direction.ASCENDING);

Then you'll have to create a FirestoreRecyclerOptions object like this:

FirestoreRecyclerOptions<ProductModel> options = new FirestoreRecyclerOptions.Builder<ProductModel>()
        .setQuery(query, ProductModel.class)
        .build();

In your activity class, create a holder class that looks like this:

private class ProductViewHolder extends RecyclerView.ViewHolder {
    private View view;

    ProductViewHolder(View itemView) {
        super(itemView);
        view = itemView;
    }

    void setProductName(String productName) {
        TextView textView = view.findViewById(R.id.text_view);
        textView.setText(productName);
    }
}

Then create an adapter which is declared as global:

private FirestoreRecyclerAdapter<ProductModel, ProductViewHolder> adapter;

And instantiate it in your activity like this:

adapter = new FirestoreRecyclerAdapter<ProductModel, ProductViewHolder>(options) {
    @Override
    protected void onBindViewHolder(@NonNull holder productViewHolder, int position, @NonNull ProductModel productModel) {
        holder.setProductName(productModel.getProductName());
    }

    @NonNull
    @Override
    public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false);
        return new ProductViewHolder(view);
    }
};
recyclerView.setAdapter(adapter);

In the end, don't forget to override the following two methods and start listening for changes:

@Override
protected void onStart() {
    super.onStart();
    adapter.startListening();
}

@Override
protected void onStop() {
    super.onStop();

    if (adapter != null) {
        adapter.stopListening();
    }
}

The result is this:

enter image description here

Edit:

If you want to display a toast message when the user clicks on an item, please add the following lines of code inside the setProductName() method from the ProductViewHolder class:

textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Toast.makeText(getApplicationContext(), productName, Toast.LENGTH_SHORT).show();
    }
});
Tuesday, June 1, 2021
 
viper
answered 7 Months ago
54

As @FrankvanPuffelen already answered in an earlier question of yours, you cannot achieve that because in your case, you should pass 2 different queries (first and second) to a single adapter, which is not possible with FirestoreRecyclerAdapter. You can either use the first query or the second with a single instance of your adapter.

A solution would be to create two different lists, containing the results from each query and combine them. Then you can pass the resulting list to another adapter, let's say an ArrayAdapter and then display the results into a ListView or even better in a RecyclerView. The problem in this case is that you will not be able to use the real-time features that the FirestoreRecyclerAdapter class provides but this approach will solve your problem.

Edit:

According to your request from the comment section, I'll give you an example on how to paginate a query on button click in the easiest way, using a ListView and an ArrayAdapter. You can achieve the same thing also using a RecyclerView when scrolling down. But to keep things simple, let's assume we have a ListView and a Button and we want to load more items to the list on every button click. For that, let's define first the views :

ListView listView = findViewById(R.id.list_view);
Button button = findViewById(R.id.button);

Let's assume we have a database structure that looks like this:

Firestore-root
   |
   --- products (collection)
         |
         --- productId (document)
                |
                --- productName: "Product Name"

And a model class that looks like this:

public class ProductModel {
    private String productName;

    public ProductModel() {}

    public ProductModel(String productName) {this.productName = productName;}

    public String getProductName() {return productName;}

    @Override
    public String toString() { return productName; }
}

Now, let's define a query with the limit set to 3.

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference productsRef = rootRef.collection("products");
Query firstQuery = productsRef.orderBy("productName", Query.Direction.ASCENDING).limit(3);

This means that on every button click, we'll load 3 more items. And now, here is the code that does the magic:

firstQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isSuccessful()) {
            List<ProductModel> list = new ArrayList<>();
            for (DocumentSnapshot document : task.getResult()) {
                ProductModel productModel = document.toObject(ProductModel.class);
                list.add(productModel);
            }
            ArrayAdapter<ProductModel> arrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, list);
            listView.setAdapter(arrayAdapter);
            lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);

            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Query nextQuery = productsRef.orderBy("productName", Query.Direction.ASCENDING).startAfter(lastVisible).limit(3);
                    nextQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                        @Override
                        public void onComplete(@NonNull Task<QuerySnapshot> t) {
                            if (t.isSuccessful()) {
                                for (DocumentSnapshot d : t.getResult()) {
                                    ProductModel productModel = d.toObject(ProductModel.class);
                                    list.add(productModel);
                                }
                                arrayAdapter.notifyDataSetChanged();
                                lastVisible = t.getResult().getDocuments().get(t.getResult().size() - 1);
                            }
                        }
                    });
                }
            });
        }
    }
});

In which lastVisible is a DocumentSnapshot object that represents the last visibile item from the query. In this case, every third one and it is declared as gloabl variable:

private DocumentSnapshot lastVisible;

Edit2: Here you have also the solution on how you can get the data from your Firestore database and display it in smaller chunks in a RecyclerView when user scrolls.

Wednesday, June 9, 2021
 
Terry
answered 6 Months ago
94

As I understand from your question, you have successfully displayed the items in your logcat, right? So in this case there are two more steps that you need to do in order to display the data in a RecyclerView.

The first step would be to create a custom adapter or if you want you can use FirestoreRecyclerAdapter and the second one is to create a holder class for your item. In the end just set the adapter to your RecyclerView and that's it.

Solution added:

For Java delopers, this is a recommended way in which you can retrieve data from a Cloud Firestore database and display it in a RecyclerView using FirestoreRecyclerAdapter.

For Kotlin developers, I'll adapt the code from the example above. Assuming you have a Firestore database structure that looks like this:

Firestore-root
    |
    --- products (collection)
           |
           --- documentIdOne (document)
           |        |
           |        --- productName: "Milk"
           |
           --- documentIdTwo (document)
           |        |
           |        --- productName: "Soy Milk"
           |
           --- documentIdThree (document)
                    |
                    --- productName: "Bacon"

A model class that looks also like this:

class ProductModel (val productName: String = "")

And a .XML file that contains a RecyclerView which also looks like this:

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"/>

To display all the product names, please follow the next steps.

Now you need to find the RecyclerView in your activity and set the LinearLayoutManager but first you need the following import:

import kotlinx.android.synthetic.main.activity_main.*

And then just use the following line of code:

recycler_view.layoutManager = LinearLayoutManager(this)

In which recycler_view is actually the id of the RecyclerView as seen in the .XML file above.

Then you need to create the root reference of your Firestore database and a Query object like this:

val rootRef = FirebaseFirestore.getInstance()
val query = rootRef!!.collection("products").orderBy("productName", Query.Direction.ASCENDING)

Then you'll have to create a FirestoreRecyclerOptions object like this:

val options = FirestoreRecyclerOptions.Builder<ProductModel>().setQuery(query, ProductModel::class.java).build()

In your activity class, create a holder class that looks like this:

private inner class ProductViewHolder internal constructor(private val view: View) : RecyclerView.ViewHolder(view) {
    internal fun setProductName(productName: String) {
        val textView = view.findViewById<TextView>(R.id.text_view)
        textView.text = productName
    }
}

Now we need to create an adapter class which in this case should look like this:

private inner class ProductFirestoreRecyclerAdapter internal constructor(options: FirestoreRecyclerOptions<ProductModel>) : FirestoreRecyclerAdapter<ProductModel, ProductViewHolder>(options) {
    override fun onBindViewHolder(productViewHolder: ProductViewHolder, position: Int, productModel: ProductModel) {
        productViewHolder.setProductName(productModel.productName)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_product, parent, false)
        return ProductViewHolder(view)
    }
}

Your item_product .XML file should like this:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/text_view""/>

Then create an adapter field which is declared as global:

private var adapter: ProductFirestoreRecyclerAdapter? = null

And instantiate it in your activity like this:

adapter = ProductFirestoreRecyclerAdapter(options)
recycler_view.adapter = adapter

In the end, don't forget to override the following two functions and start listening for changes:

override fun onStart() {
    super.onStart()
    adapter!!.startListening()
}

override fun onStop() {
    super.onStop()

    if (adapter != null) {
        adapter!!.stopListening()
    }
}

The result is this:

enter image description here

As you can see using Kotlin, the code is even simpler and are less lines of code but remember official documentations will never provide you particular code, you'll have to create your self.

Tuesday, August 3, 2021
 
bumperbox
answered 4 Months ago
83

You are getting a "blank screen" because the name of the properties in your class do not match the name of the properties in the database. See, you have a field named mPlaceTitle in your Place class, while in the database is called placeTitle and this is not correct. To solve this, you can simply change the name of all properties in your class to match the one in the database, which are named correct according to the Java Naming Conventions regarding variables.

Thursday, September 2, 2021
 
themihai
answered 3 Months ago
43

This is a very common practice when it comes to Firestore, to store the number of likes in the Firebase Realtime database, otherwise you'll be charged for every read/write operation as explained in my answer from this post. So using Firebase Realtime database you can host the number of likes at no cost.

So, how can be done? First of all, you are guessing right. The number of likes should be added beneath the postId like this:

Firebase-root
   |
   --- likes
         |
         --- postIdOne: numberOfLikes //must be stored as an integer
         |
         --- postIdOTwo: numberOfLikes //must be stored as an integer
         |
         --- //and so on

To achive what you want, you need to follow the next steps.

  1. Every time you you add a new post, add the corresponding post id in Firebase Realtime database like above by setting the value of that particular post id to 0.
  2. Every time you get a new like increase the value of that postId by one. Every time a user retracts a like, decrease the value of that postId by one. To achieve this and also to have consistent data, I recommend you use Firebase Transactions.
  3. Then in your adapter class, where you are displaying data from Firestore, when you want to set the number of likes to a view, just attach a listener on that particular post id node and get the number of likes. Inside the onDataChange() set that number to a TextView like this:

    DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
    DatabaseReference noOfLikesRef = rootRef.child("likes").child(postId);
    ValueEventListener valueEventListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            String numberOfLikes = "(" + dataSnapshot.getValue() + ")";
            numberOfLikesTextView.setText(numberOfLikes);
        }
    
        @Override
        public void onCancelled(DatabaseError databaseError) {}
    };
    noOfLikesRef.addListenerForSingleValueEvent(valueEventListener);
    

That's it!

Thursday, September 2, 2021
 
Ramy Al Zuhouri
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