Welcome back! After having an introduction to what the recyclerview is, it is time to start digging into the details.
This time, we will be talking about the adapter and viewholder objects and how they work together. At this point, we know what they are and what roles they play, now it is time to take that knowledge into practice.
If this doesn’t make a lot of sense to you yet or this is your first post of the series, spare a couple of minutes to read theintroduction to the recyclerview and then come back.
Step One: Define our tasks.
The first step to accomplishing anything is to have a clear goal and a series of steps and tasks to help us get us there. In this case, our goal is learning about adapters and viewholders and to achieve that, we are going to use a sample project, something simple that can illustrate the main usage of the adapters and viewholder without worrying too much about other details.
This android application will list all the countries with name and flag of all the folks that have come here to read at least once. When we are done we should have something similar to the image below
Step Two: Add UI and resources.
Now with a clear goal to pursue, the next step is adding the visual resources that the user will be interacting with, in this case, the flag images and the layout of the elements (more on this later).
Adding flag images
For now, we are just going to import the images into our project to be able to use them when we are creating the UI (on a later step).
To get the images, simply go here and copy all the drawable folders into your project’s res folder and wait for the project to sync.
Creating the UI
The recyclerview has three components it uses to display data to the user:
- The recyclerview tag inside the fragment or activity that will contain it.
- The Layout that describes how to display each individual element of the recyclerview.
- The LayoutManager that describes how the elements are arranged inside the recyclerview.
We first start defining the recyclerview tag. In our case, we just need to edit the activity_main.xml layout file, removing all but the root container and then adding the recyclerview tag, ensuring it takes all the available space, as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/countryRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/country_item" />
<LinearLayout/>
With that set, we can continue with the layout for each of the elements. Defining the layout is very straightforward, similarly to what we did before, we create a new xml layout file inside the layout folder, name it country_item.xml and define it’s content the same way we would do with an activity or fragment.
In our case, to keep it simple, we just have the flag image on the left side, the text on the right side and a view serving as a line separator for each element. Here’s the code:
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/country_item_size">
<ImageView
android:id="@+id/countryFlag"
android:layout_width="@dimen/flag_size"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
tools:srcCompat="@drawable/dominican_republic"
android:padding="@dimen/normal_padding"
android:contentDescription="@string/country_flag_description"/>
<TextView
android:id="@+id/countryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/countryFlag"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Dominican Republic"
android:textSize="@dimen/normal_text_size"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/country_element_separator"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
A few things to notice here:
- I used a ConstraintLayout because I find it easier (and more performant) to describe the layout this way, this is not the only way to do it, you could have used a LinearLayout or even a GridLayout.
- I use @dimen/key and @color/key to prevent using hardcoded size and color values. This works just like @strings/key and the strings.xml file.
- I avoid using match_parent on the height property to prevent a single element from taking all the screen space. In this case, I used a specific value but wrap_content could be used if it made sense to you.
- I use the tools namespace to define values that are only visible for the previewer such as the name and image of the country.
What about the layout manager? Well, for this case, we are going to use the LinearLayoutManager object which organizes the layout into a list of elements where each row takes all the width available, we are setting it up in our final step.
Step Three: Implementing the Adapter and ViewHolder classes
With all the resources at hand, we can now focus on getting data to the recyclerview. We do that with the adapter and ViewHolder objects.
If you remember from the last post, the adapter object is in charge to get data from the model, process it and send it to the recyclerview using ViewHolder objects as a way to bind (display) the data to the UI since they are representations from the kotlin/java side of each row’s element.
With that said, it is time to create our adapter class and get the data through. The first step is creating the adapter and viewholder classes each one inheriting from their corresponding superclasses
- The adapter class inherits from
Adapter<VH extends RecyclerView.ViewHolder>
- The viewholder class inherits from
RecyclerView.ViewHolder
Then, we will have to implement the inherited methods from the base adapter class, each one with a different purpose:
- OnCreateViewHolder: Returns the viewholder (representation from kotlin/java code) for each row
- GetItemCount: Returns the total number of elements
- OnBindViewHolder: Bind the data from the model to the UI
When we add all that, we end up with the skeleton code for both classes, as follows:
package com.raulmonteroc.countryflags
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.raulmonteroc.countryflags.CountryAdapter.FlagViewHolder
class CountryAdapter : RecyclerView.Adapter<FlagViewHolder>() {
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): FlagViewHolder {
}
override fun getItemCount(): Int {
}
override fun onBindViewHolder(p0: FlagViewHolder, p1: Int) {
}
class FlagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
Now, it’s time to implement all the methods:
- For the onCreateViewHolder , we just need to return a viewholder object and since the view holder constructor needs a view, we’ll just inflate the XML layout we just created earlier for the rows and pass that view instance to the new viewholder’s constructor.
- For the getItemCount , we will pass a collection of elements to the adapter’s constructor to hold all the elements and use that collection’s size to return the number of elements involved.
- For the *onBindViewHolder, * we will create a bind method on the viewholder object where we get the element from the layout(the layout we passed in the first method we implemented) and assign the values of the model object we pass through, just like we would do with any activity or fragment.
I know this could be a bit abstract without code to see, so here’s the completed adapter code with all the functionality described above.
package com.raulmonteroc.countryflags
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.raulmonteroc.countryflags.CountryAdapter.FlagViewHolder
class CountryAdapter(private val countries: List<Country>) : RecyclerView.Adapter<FlagViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlagViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.country_item, parent,false)
return FlagViewHolder(view)
}
override fun getItemCount() = countries.size
override fun onBindViewHolder(viewHolder: FlagViewHolder, position: Int) = viewHolder.bind(countries[position])
class FlagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val name:TextView = itemView.findViewById(R.id.countryName)
private val flag:ImageView = itemView.findViewById(R.id.countryFlag)
fun bind(country : Country) {
name.text = country.name
flag.setImageResource(country.flagImageResource)
}
}
}
Step Four: Connect the recyclerview to the activity.
Now that we have all the pieces working, what’s left is to connect them together, more specifically, make the adapter, recyclerview and activity work like a fine-tuned orchestra.
We do that by setting up some recyclerview properties and make sure they are called from the onCreate method, here are the steps:
- Get an instance of the recyclerview using findViewById
- Set the layout manager to the LinearLayoutManager to render the Recylerview as a list of items
- Set the adapter property
package com.raulmonteroc.countryflags
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
class MainActivity : AppCompatActivity() {
lateinit var countryRecyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView()
}
private fun setupRecyclerView() {
countryRecyclerView = findViewById(R.id.countryRecyclerView)
countryRecyclerView.layoutManager = LinearLayoutManager(this)
countryRecyclerView.adapter = CountryAdapter(CountrySeed().seed())
}
}
You can note here a seed() method is passed when initializing the adapter, this is simply to provide the data the recyclerview is going to display.
Here’s the code
package com.raulmonteroc.countryflags
class CountrySeed {
fun seed(): List<Country> {
val countries = listOf<Country>(
Country("Brazil", R.drawable.brazil),
Country("Canada", R.drawable.canada),
Country("China", R.drawable.china),
Country("Dominican Republic", R.drawable.dominican_republic),
Country("Germany", R.drawable.germany),
Country("India", R.drawable.india),
Country("Netherlands", R.drawable.netherlands),
Country("norway", R.drawable.norway),
Country("Peru", R.drawable.peru),
Country("Philippines", R.drawable.philipines),
Country("Poland", R.drawable.poland),
Country("Romania", R.drawable.romania),
Country("South Africa", R.drawable.south_africa),
Country("Spain", R.drawable.spain),
Country("Sweden", R.drawable.sweden),
Country("United Kingdom", R.drawable.united_kingdom),
Country("United States", R.drawable.united_states)
)
countries.sortedBy { it.name }
return countries
}
}
Coming up next
Now we got a working recyclerview, with data and a custom layout for its elements, and only with a bit of effort, that’s great right?.
Following up in our recyclerview series, we will be updating this project to enable the recyclerview to respond to touch gestures such as drag and swipes
See you then!
Top comments (3)
This is great, especially the seed() method. However, you do not need
findViewById()
anymore. We use View Binding instead: developer.android.com/topic/librar...Right! the new Binding capabilities android brought in.
I haven't tried yet on Android but I'm a big fan of MVVM on Xamarin. This is a great excuse to give it a try.
Thanks for the feedback.
hello man I am a new developer . Here I get an error
fun seed(): List
Country is unresolved