Handling click events like a pro with RecylverView and MVVM pattern on android

Handling click events like a pro with RecylverView and MVVM pattern on android

Displaying a list of items in an android application and clicking on those items is a popular pattern. Anyone going by the tag of an Android Developer should be able to handle this with ease so I will not get into those details here. However, it is not enough to simply click on an item and do something. You should do this in line with the pattern you are using, which, in this case, is MVVM. So how do you click an item on a RecylerView with the MVVM pattern?

Let us code

The app we will use is a news application that simply displays news when it is opened and then allows the user to click on items. Once clicked, the item opens a web-page with an implicit intent. Examine the following click listener, which passes the clicked item in a lambda. Note that I am using data binding in this project.

class OnNewsClickListener(val clickListener: (news: News?) -> Unit) {
    fun onClick(news: News?) = clickListener(news)
}

In your adapter, you will initialize it like this.

class NewsAdapter(private val clickListener: OnNewsClickListener) :
    ListAdapter<News, NewsAdapter.NewsViewHolder>(NewsDiffCallback()) {

    ...

    override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        holder.bind(getItem(position), clickListener)
    }

    class NewsViewHolder... {

        fun bind(news: News, clickListener: OnNewsClickListener) {
            binding.clickListener = clickListener
        }
    }
}

In my layout, I will then click on the whole enclosing ViewGroup, which is a CardView in my case. Note the onClick attribute for the card.

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="clickListener"
            type="com.onefootball.utils.OnNewsClickListener" />
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/item_card_margin"
        android:layout_marginTop="@dimen/item_card_margin"
        android:layout_marginEnd="@dimen/item_card_margin"
        android:clickable="true"
        android:focusable="true"
        android:foreground="?android:attr/selectableItemBackground"
        android:onClick="@{() -> clickListener.onClick(news)}"
        app:cardCornerRadius="@dimen/item_card_margin"
        app:cardElevation="@dimen/item_card_margin_small">

    </androidx.cardview.widget.CardView>
</layout>

Once you have done this, your basic set up is ready. Time to move to the ViewModel now where the magic happens. First off, a click action is an event, which we will wrap in a handy event class. You can access the event class in the repo if you wish here .

In your ViewModel, declare the following:

    /*The internal MutableLiveData that stores the event of a click input */
    private val _navigateToSelectedNews = MutableLiveData<Event<News>>()

    /**The external immutable LiveData for the click event*/
    val navigateToSelectedNews: LiveData<Event<News>>
        get() = _navigateToSelectedNews

    /**Called when a user clicks on a News item*/
    fun displayNewsDetails(news: News) {
        _navigateToSelectedNews.value = Event(news)
    }

With this, the click event is passed to the ViewModel and handled there. Using our beloved Observer pattern in the activity, we shall have the following. First, create an object of the adapter.

    private val onNewsClickListener = OnNewsClickListener { news ->
        news?.let { it -> viewModel.displayNewsDetails(it) }
    }

    val myAdapter = NewsAdapter(onNewsClickListener)

Notice that the news item clicked is passed on to the ViewModel in the lambda where the click listener is declared.

Finally, I have created a helper method to create the intent to launch a web-page. You can as well do this in your onCreate() if you want. See the following method.

    private fun observeNewsClickEvents() {
        viewModel.navigateToSelectedNews.observe(this, Observer {
            it.getContentIfNotHandled()?.let { news ->
                startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(news.newsLink)))
            }
        })
    }

Find the repo containing the whole project here.

That's it!

That is all you need to do to handle clicks on a RecylverView with MVVM like a pro. It is really simple and makes things nice and clean for you. Happy coding!

PS: Let us connect on GitHub , Twitter , and LinkedIn .