# Android: RecyclerView

###### tags: `Android Widgets`
# Outline
[TOC]
# What is RecyclerView?
A flexible view which provides a large amount of data. You surely used it before when checking emails/booking flights in applications.

# What do you need?
1. XML RecyclerView
2. Adapter<RecyclerView.Viewholder>
3. RecyclerView.Viewholder
4. XML format for Viewholder
## What is Viewholder and Adapter?

Viewholder and adapter are the main elements to build a RecyclerView. Let's start from the smallest part of RecyclerView:
**VIEWHOLDER**
>**++*What is it?*++**
You can think of android recyclerview as a collection of Viewholders. In other words, if you do not have viewholders, then you will not have any items in your RecyclerView. [color=#187fce]
> **++*How to design it?*++** [color=#187fce]
You will have to make an XML file in layout folder. You must design it as how you will want to see each RecyclerView item.
Later on, it will be set up as viewholder design base in Adapter.
**ADAPTER**
>**++*What is it?*++**
It is the one in charge of setting up the viewholders and the amount displayed of them displayed in RecyclerView.
Also, if there is a change in information, it will be the one in charge to notify the changed done.
Note: function notifySetChanged();[color=#187fce]
# How to make one?
## Step1: Set up RecyclerView in XML file
Drag the RecyclerView to the layout you want to put it in.
**Preview in XML:**

:::info
**Note:**
1. If there is a **Download Icon** next to RecylerView, remember to download it fist in order to use it.
2. Give an id to the RecyclerView so as to findViewById() later in activity.
:::
## Step2: New a XML file in layout folder
This XML will be used as base for the viewholder. In this case, 3 TextViews were put inside it.
**Preview:**

**Code:**
``` java==
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:orientation="vertical"
android:padding="10dp"
android:background="@color/viewholder"
android:layout_marginBottom="5dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_name"
android:textSize="12sp"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_capital"
android:textSize="12sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_region"
android:textSize="12sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
```
## Step3: New a class extends RecyclerView.Viewholder
1. **MUST** extend from RecyclerView.Viewholder
2. According to the XML you made for viewholder, you must findViewById for each material inside them.
:::info
**Note:**
Remember to use ++itemView.findViewById()++
**What is itemView?**
You can think of it as the XML file for the Viewholder, and you find your ViewId from it.
:::
**Code:**
```java==
public class MVCViewholder extends RecyclerView.ViewHolder {
public TextView tv_name;
public TextView tv_capital;
public TextView tv_region;
public MVCViewholder(@NonNull View itemView) {
super(itemView);
this.tv_name = itemView.findViewById(R.id.tv_name);
this.tv_capital = itemView.findViewById(R.id.tv_capital);
this.tv_region = itemView.findViewById(R.id.tv_region);
});
}
}
```
## Step4: New a class extends RecyclerView.Adapter<RecyclerView.Viewholder>
1. Override the methods from RecyclerView.Adapter<Viewholder>
**Note:** You will notice that you did not override them, if your class is underlined in red.
2. Inflate View in onCreateViewHolder
```java==
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mvc_rv_viewholder, viewGroup, false);
```
3. Bind your information/image to the items in the XML file in onBindViewHolder.
Here, you can get the position it is in. So, by using the position, you can get the information you want from the Arraylist, List, LinkedList, etc..
**Note:**
1. Remember you must have **a List/ArrayList with your data** inside in order to set it in the viewholder.
2. Use **notifyDataSetChanged()** in order to let RecyclerView know that there was a change in list/arraylist.
**Code:**
``` java==
public class MVCAdapter extends RecyclerView.Adapter<MVCViewholder> {
private ArrayList<Country> countryList;
public MVCAdapter() {
this.countryList = new ArrayList<>();
}
@NonNull
@Override
public MVCViewholder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mvc_rv_viewholder, viewGroup, false);
return new MVCViewholder(view);
}
@Override
public void onBindViewHolder(@NonNull MVCViewholder mvcViewholder, int i) {
mvcViewholder.tv_name.setText(countryList.get(i).countryName);
mvcViewholder.tv_capital.setText(countryList.get(i).capitalName);
mvcViewholder.tv_region.setText(countryList.get(i).regionName);
}
@Override
public int getItemCount() {
return countryList.size();
}
public void setCountryList(ArrayList<Country> newList){
this.countryList.clear();
countryList.addAll(newList);
notifyDataSetChanged();
}
}
```
## Step4: Activitiy Set up
1. Instantiate RecyclerView and findViewById()
2. New and setAdapter to RecyclerView
3. New LayoutManager and setLayoutManager to RecyclerView
4. Remember to give list/arraylist to adapter and notifyDataSetChanged()
``` java==
private RecyclerView rv_country;
private MVCAdapter mvcAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
this.rv_country = findViewById(R.id.rv_country);
this.mvcAdapter = new MVCAdapter();
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
this.rv_country.setAdapter(mvcAdapter);
this.rv_country.setLayoutManager(layoutManager);
}
//Update arraylist values
public void setValues(ArrayList<Country> countryList){
this.mvcAdapter.setCountryList(countryList);
}
```
# onClick Viewholder
**Activity:**
1. Set up an interface
``` java==
public interface onClickI{
public void onClick(int i);
}
```
2. Pass it to Adapter and you set up onClick function in it.
```java==
this.mvcAdapter = new MVCAdapter(new onClickI() {
@Override
public void onClick(int i) {
//Decide what to do after onClick!
Toast.makeText(MVCActivity.this, "onClick Viewholder number: " + i, Toast.LENGTH_SHORT).show();
}
});
```
**Adapter:**
Pass the interface to adapter and later on to viewholder.
```java==
private ArrayList<Country> countryList;
private MVCActivity.onClickI onClickI;
public MVCAdapter(MVCActivity.onClickI onClickIn) {
this.countryList = new ArrayList<>();
this.onClickI = onClickIn;
}
@NonNull
@Override
public MVCViewholder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mvc_rv_viewholder, viewGroup, false);
return new MVCViewholder(view, onClickI);
}
```
**Viewholder:**
In viewholder you will be able to know which viewholder you are clicking by using **getLayoutPosition()**.
After knowing which one is being pressed, you can get the information from the list/arraylist in the activity.
``` java==
public MVCViewholder(@NonNull View itemView, final MVCActivity.onClickI onClickIn) {
super(itemView);
this.tv_name = itemView.findViewById(R.id.tv_name);
//Here you will return the position you are clicking back to Activity
this.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickIn.onClick(getLayoutPosition());
}
});
}
```
# Avoid Blinking on notifyDataSetChanged()
## **Reason for blinking:**
RecycleView disabled stable IDs as default. So after notifyDataSetChanged(), RecyclerView.Adapter won't assign the same ViewHolder to original item in the data set, causing to blink.
## **Solve Blinking:**
Reuse the same viewholder and view, by using stable id to identify previous viewholder and view.
### **Steps for solution**
1. **setHasStableIds(true)**
```kotlin=
override fun setHasStableIds(hasStableIds: Boolean) {
super.setHasStableIds(true)
}
```
2. **override getItemId(int position)**
```kotlin=
override fun getItemId(position: Int): Long {
val dealResult : HostBasic = dealList[position]
return dealResult.id!!.toLong()
}
```
**Information from Url:** https://medium.com/@hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2
# Did I reach the bottom of RV?
## Introduction
Sometimes we may want to just load some data only, and later fetch more, in order to reduce the time to load the amount of data. Therefore, sometimes we need to know whether we reached the bottom or not.
## addOnScrollListener
For sure many of you will just first think of this listener, which can tells you whether the recyclerview is being scrolled or not, andwhich position it is in.
When we add this listener, we can **override onScrollStateChanged && onScrolled**. We could use onScrolled and try to calculate where it reached... but it is a pain to do so. Therefore we override **onScrollStateChanged**.
**Note:**
When we reach the bottom RecyclerView normally stops, unless it is infinite scrolling. So we will check the **state** it is in. Apart from it, we need to know the direction it is being scrolled.
### RecyclerView State
>**RecyclerView.SCROLL_STATE_IDLE** = 0
**RecyclerView.SCROLL_STATE_DRAGGING** = 1; [color=pink]
### RecyclerView Direction (canScrollVertically/canScrollHorizontally )
We may believe this **canScrollVertically** is used to check whether it can be scrolled. But actually we use it to know the direction it is facing.
**Vertical:**
-1 means SCROLL_UPWARDS
1 means SCROLL_DOWNWARDS
**Note:** In our example DOWN_DIRECTION is 1
```kotlin=
private fun setRvListener(){
getRv().addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(rv: RecyclerView, newState: Int) {
super.onScrollStateChanged(rv, newState)
if (!rv.canScrollVertically(DOWN_DIRECTION) && newState == RecyclerView.SCROLL_STATE_IDLE) {
Log.d("BOTTOMCHECK","REACHED BOTTOM");
}
Log.d("BOTTOMCHECK","non end $newState");
}
})
}
```
## Interesting Notes:
* If RV only shows 1 per view, then even if you use method **getChildCount**, it will only return 1 as size, even if real data size is bigger than 1. The same happens with **getChildAt**(gets view), it will return NULL if you try getChildAt(5), when your view only displays 1. Therefore, check your view before calling any of those methods.