はじめに
RecyclerViewで表示しているリストのタップイベントを実装しようとした時に、イベントの実装をどこに置くのがいいのか迷うことがありました。
今まではViewModelに処理を持たせてListAdapterにViewModelを渡し、ListAdapter→ViewModelにアクセスするように作っていたのですが、公式サイト(UI イベント)を見てみると、その方法は推奨されないとのこと。
警告: ViewModel を RecyclerView アダプターに渡すことは、アダプターと ViewModel クラスを密結合させることになるため、おすすめしません。
公式では、ListAdapterに渡すリストデータにイベントの実装を含めるという方法が推奨されていました。
今回は公式サイトの例を参考にRecyclerViewを使い、リストタップ → ViewModelの処理を呼び出す方法をまとめたいと思います。
実装
今回は「RecyclerViewのリストをタップ → ViewModelで実装しているログ出力を実行」となる処理を作ります。
サンプルで作ったソースはこちらになります。
そもそものRecyclerViewやViewModelの実装についてはこの記事では触れませんので、ご留意ください🙇♂️
リストのデータモデルを作成する
以下のようなデータクラスを作成します。
ここで重要になるのがclickActionの定義です。
リストとして渡すデータに、クリックしたときの処理を含めるようにします。
data class LinkItemModel(
val title: String,
val url: String,
val clickAction: () -> Unit // ここが大事
)
ViewModelで、タップ時の処理を含めたリストデータを作成
ViewModelにて、先ほど作成したLinkItemModelを使用し、ListAdapter渡すデータを作成します。
clickActionの箇所には、リストタップ時に実行したい処理を含めてデータの作成を行います。
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に設定した処理が実行されるようになります。
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通信を行う、といったこともできるかと思います。
どこか参考になるところがあれば幸いです!