How to create a RecyclerView with custom Adapter in Kotlin
April 20, 2021
Tags: Android, Android Basics, Github Repository App, RecyclerView,
Components of a RecyclerView
RecyclerView
: The view on the screen where the list will be displayed.
Item/data
: The list of items that will be displayed.
Adapter
: Maps the data to views.
ViewHolders
- A pool of views for RecyclerView to use and reuse to display. ViewHolders are created and reused in the Adapter.
Overview
- Add required dependency.
- Create layout for your list items.
- Add RecyclerView to the layout.
- Create a ViewHolder.
- Create an Adapter.
- Create and connect RecyclerView and Adapter in the Activity/Fragment.
- Connect or change the data source if needed.
Step 1: Add dependencies
Add the dependency to your app level gradle file.
implementation "androidx.recyclerview:recyclerview:1.1.0"
Step 2: Create layout for the list items
Create a listitem.xml in the layout folder (res -> layout -> listitem.xml) and create a layout. This layout defines how each of the item in the list will look. Make sure to give ids to widgets that will change with the data, for example any TextViews where you might need to change the text or visibility like title, count, etc
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp">
<TextView
android:id="@+id/tv_repo_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"/>
...
...
</LinearLayout>
Step 3: Add RecyclerView to the layout
Add the recycler view widget to your Activity or Fragment layout wherever you want to display the RecyclerView.
NOTE: I have defined my layout manager and orientation here. You can change the layout manager and orientation here or when you create an instance of your custom adapter in the Activity/Fragment.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_repo_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical"/>
Step 4: Create a ViewHolder
Create a new Kotlin class for the ViewHolder. Here we will find all the views from our list view and bind data to them. I have create a bind
function for that takes in a GithubRepository
which is a data class that I created to hold Github repo data and binds data from the object to the views.
// RepositoryViewHolder.kt
class RepositoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
// Find all the views of the list item
private val mRepositoryName: TextView = itemView.findViewById(R.id.tv_repo_name)
private val mRepositoryDescription: TextView = itemView.findViewById(R.id.tv_repo_description)
private val mRepositoryStarCount: TextView = itemView.findViewById(R.id.tv_star_count)
private val mRepositoryLanguage: TextView = itemView.findViewById(R.id.tv_language)
private val mRepositoryUpdatedAt: TextView = itemView.findViewById(R.id.tv_updated_at)
private val mRepositoryLicense: TextView = itemView.findViewById(R.id.tv_license)
// Show the data in the views
fun bind(repo: GithubRepository) {
val name = repo.name
val description = repo.description
val stargazersCount = repo.stargazersCount
val language = repo.language
val updatedAt = repo.updatedAt
val license = repo.license
mRepositoryName.text = name
mRepositoryStarCount.text = stargazersCount.toString()
mRepositoryUpdatedAt.text = updatedAt
// Since the data in these can be null we check and bind data
// or remove the view otherwise
bindOrHideTextView(mRepositoryDescription, description)
bindOrHideTextView(mRepositoryLanguage, language)
bindOrHideTextView(mRepositoryLicense, license)
}
private fun bindOrHideTextView(textView: TextView, data: String?) {
if (data == null) {
textView.visibility = View.GONE
} else {
textView.text = data
textView.visibility = View.VISIBLE
}
}
}
Step 5: Create an Adapter
Start building your adapter by creating a new Kotlin class. Implement RecyclerView.Adapter<YourViewHolder>()
. It should look something like this
class GithubRepositoryAdapter() : RecyclerView.Adapter<RepositoryViewHolder>() {
}
At this point you would see the red squiggly like under you class name, implement the methods from the prompt. Now you need to complete the three functions:
onCreateViewHolder
: This function is called whenever a new viewHolder needs to be created, so you just need to inflate the list item layout you created earlier and pass it on to a new instance of a your ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder {
val context = parent.context
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.repository_list_item, parent, false)
return RepositoryViewHolder(view)
}
onBindViewHolder
: is called to attach data to a ViewHolder. Here you change the text, color, whatever needs to be done to the list item views according to the data being displayed. I created a bind function in the ViewHolder earlier for convenience which I use here, if you want you could do everything in the bind function here instead, I find this to be cleaner.
override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) {
holder.bind(mAllRepositories!![position])
}
getItemCount
: Here you return the total number of items in the RecyclerView.
override fun getItemCount(): Int {
return if (mAllRepositories == null) 0 else mAllRepositories!!.size
}
So your adapter should look something like this. I also created a function which I can call from the Activity or Fragment to change/update the data.
class GithubRepositoryAdapter() : RecyclerView.Adapter<RepositoryViewHolder>() {
private val TAG = javaClass.simpleName
private var mAllRepositories: Array<GithubRepository>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder {
val context = parent.context
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.repository_list_item, parent, false)
return RepositoryViewHolder(view)
}
override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) {
holder.bind(mAllRepositories!![position])
}
override fun getItemCount(): Int {
return if (mAllRepositories == null) 0 else mAllRepositories!!.size
}
fun setmAllRepositories(repositories: Array<GithubRepository>?) {
mAllRepositories = repositories
notifyDataSetChanged()
}
}
Step 6: Create and connect RecyclerView and Adapter in the Activity/Fragment
Create variables for the RecyclerView and the Adapter in your Activity/Fragment
class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
...
...
private lateinit var mRepositoryRecyclerView: RecyclerView
private lateinit var mGithubRepositoryAdapter: GithubRepositoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
...
mRepositoryRecyclerView = findViewById(R.id.rv_repo_list)
mGithubRepositoryAdapter = GithubRepositoryAdapter()
mRepositoryRecyclerView.adapter = mGithubRepositoryAdapter
...
...
}
...
...
}
Step 7: Connect or change the data source if needed.
If you have static data a good option is to pass that data as parameter to the Adapter. I am using a ViewModel here with LiveData to observe changes in the list of GitHubRepositories and then send the changes to the adapter.
viewModel.repos.observe(this, { githubRepositories ->
mRepos = githubRepositories
mGithubRepositoryAdapter.setmAllRepositories(githubRepositories)
mRepositoryRecyclerView.visibility = View.VISIBLE
mProgressBar.visibility = View.GONE
})
Hopefully you feel more comfortable with using RecyclerView. If you find any errors, have feedback or just want to say hi please don’t hesitate to leave a comment below.