【Android】RecyclerViewのイベントの実装

## はじめに
RecyclerViewで表示しているリストのタップイベントを実装しようとした時に、イベントの実装をどこに置くのがいいのか迷うことがありました。
今まではViewModelに処理を持たせてListAdapterにViewModelを渡し、ListAdapter→ViewModelにアクセスするように作っていたのですが、[公式サイト](https://developer.android.com/topic/architecture/ui-layer/events?hl=ja&_gl=1*8i836n*_up*MQ..*_ga*MTI2NTI1MjQxMC4xNzE3NTg2NjE4*_ga_6HH9YJMN9M*MTcxNzY3MTI0MS40LjAuMTcxNzY3MTI0MS4wLjAuMA..)(UI イベント)を見てみると、その方法は推奨されないとのこと。
>警告: ViewModel を RecyclerView アダプターに渡すことは、アダプターと ViewModel クラスを密結合させることになるため、おすすめしません。

公式では、ListAdapterに渡すリストデータにイベントの実装を含めるという方法が推奨されていました。

今回は公式サイトの例を参考にRecyclerViewを使い、リストタップ　→ ViewModelの処理を呼び出す方法をまとめたいと思います。

## 実装
今回は「RecyclerViewのリストをタップ　→ ViewModelで実装しているログ出力を実行」となる処理を作ります。
サンプルで作ったソースは[こちら](https://github.com/Nkot117/RecyclerViewSample)になります。
そもそものRecyclerViewやViewModelの実装についてはこの記事では触れませんので、ご留意ください🙇‍♂️

### リストのデータモデルを作成する
以下のようなデータクラスを作成します。
ここで重要になるのが**clickAction**の定義です。
リストとして渡すデータに、クリックしたときの処理を含めるようにします。
``` LinkItemModel.kt
data class LinkItemModel(
    val title: String,
    val url: String,
    val clickAction: () -> Unit // ここが大事
)
```

### ViewModelで、タップ時の処理を含めたリストデータを作成
ViewModelにて、先ほど作成したLinkItemModelを使用し、ListAdapter渡すデータを作成します。
clickActionの箇所には、リストタップ時に実行したい処理を含めてデータの作成を行います。

``` MainViewModel.kt
class MainViewModel : ViewModel() {
    private val _items: MutableStateFlow<List<LinkItemModel>> = MutableStateFlow(emptyList())
    val items = _items.asStateFlow()

    init {
        val linkList = listOf(
            arrayOf("https://www.google.com", "Google"),
            arrayOf("https://www.yahoo.com", "Yahoo"),
            arrayOf("https://www.bing.com", "Bing"),
            arrayOf("https://www.duckduckgo.com", "DuckDuckGo"),
            arrayOf("https://www.ask.com", "Ask"),
            arrayOf("https://www.aol.com", "AOL"),
            arrayOf("https://www.wolframalpha.com", "Wolfram Alpha"),
            arrayOf("https://www.yandex.com", "Yandex"),
            arrayOf("https://www.baidu.com", "Baidu"),
        )

        _items.value = linkList.map {
            LinkItemModel(
                it[1],
                it[0]
            ) {
                // リストタップ時の処理を定義
                printLog("Clicked on ${it[1]}：${it[0]}") 
            }
        }
    }

    private fun printLog(message: String) {
        println("MainViewModel: $message")
    }
}
```
### Adapterにクリックイベントを設定
Adapterにリストがタップされたときのイベントを設定します。
この時、実行される処理としては**linkItemModel.clickAction()**を設定します。
こうすることで項目タップ時には、先ほどViewModelでclickActionに設定した処理が実行されるようになります。
``` LinkListAdapter.kt
class LinkListAdapter :
    ListAdapter<LinkItemModel, LinkListAdapter.ViewHolder>(DIFF_UTIL_ITEM_CALLBACK) {

    class ViewHolder(
        private val binding: ListItemBinding,
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(linkItemModel: LinkItemModel) {
            binding.textView.text = linkItemModel.title
            binding.root.setOnClickListener {
                linkItemModel.clickAction()
            }
        }
    }
                ・
                ・
                ・
}
```

このような実装にすることで、ListAdapterにViewModelを渡すことなく、ViewModelの処理を実行できるようになります。

## おわりに
ListAdapterからViewModelの処理を実行する方法についてまとめました。
ListAdapterがViewModel全体にアクセスするのではなく、一部のアクセスに限られるので、実装の安全性も増すかと思います。
今回作成したサンプルアプリでは、タップしたらログ出力するだけですので、特に意味がありませんが、タップした時にDB操作やAPI通信を行う、といったこともできるかと思います。

どこか参考になるところがあれば幸いです！

## 参考
[UI イベント](https://developer.android.com/topic/architecture/ui-layer/events?hl=ja&_gl=1*i1i52f*_up*MQ..*_ga*MTI2NTI1MjQxMC4xNzE3NTg2NjE4*_ga_6HH9YJMN9M*MTcxNzgxNDY3OC41LjAuMTcxNzgxNDY3OC4wLjAuMA..)
[RecyclerViewSample](https://github.com/Nkot117/RecyclerViewSample)