Android Handle RecyclerView Click Event With LiveData

Propagate RecyclerView Click Event to Parent Activity/Fragment

There are many ways to do this, but my favourite way is using LiveData due to the loose coupling.

  • We create a ViewModel to keep val selectItemEvent = SingleLiveEvent<Quote?>()
  • SingleLiveEvent is actually a modified MutableLiveData to allow single consumption/observe only. The reason is to avoid double consumption/observe during configuration change/screen rotation.
  • ViewModel is passed into RecyclerView.Adapter for click event messaging (viewModel.selectItemEvent.value = item), while the parent activity/fragment can observe the event.

MyQuoteListFragment.kt

class MyQuoteListFragment : Fragment() {    companion object {        @JvmStatic        fun newInstance() =            MyQuoteListFragment().apply {                arguments = Bundle().apply {                    // putInt(ARG_COLUMN_COUNT, columnCount)                }            }    }    private lateinit var viewModel: MyQuoteListViewModel    private lateinit var adapter: MyQuoteAdapter    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        arguments?.let {            // columnCount = it.getInt(ARG_COLUMN_COUNT)        }    }    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        return inflater.inflate(R.layout.myquote_list, container, false)    }    override fun onActivityCreated(savedInstanceState: Bundle?) {        super.onActivityCreated(savedInstanceState)        viewModel = ViewModelProviders.of(this).get(MyQuoteListViewModel::class.java)        val items = listOf(            Quote("Premature optimization is the root of all evil", null),            Quote("Any sufficiently advanced technology is indistinguishable from magic.", "Arthur C. Clarke"),            Quote("Content 01", "Source"),            Quote("Content 02", "Source"),            Quote("Content 03", "Source"),            Quote("Content 04", "Source"),            Quote("Content 05", "Source")        )        adapter = MyQuoteAdapter(viewModel)        adapter.replaceItems(items)        list.adapter = adapter        // listen to recyclerView click event        viewModel.selectItemEvent.observe(this, Observer{            if (it != null) {                Timber.d("selected=${it.content}")            }        })    }    class MyQuoteAdapter(val viewModel: MyQuoteListViewModel) : RecyclerView.Adapter<MyQuoteAdapter.ViewHolder>() {        private var items = listOf<Quote>()        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {            val view = LayoutInflater.from(parent.context)                .inflate(R.layout.myquote_list_item, parent, false)            return ViewHolder(view)        }        override fun onBindViewHolder(holder: ViewHolder, position: Int) {            val item = items[position]            holder.contentTextView.text = item.content            holder.sourceTextView.text = item.source            holder.itemView.setOnClickListener {                // fire recyclerView click event                viewModel.selectItemEvent.value = item            }                    }        fun replaceItems(items: List<Quote>) {            this.items = items            notifyDataSetChanged()        }        override fun getItemCount(): Int = items.size        inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer    }}

MyQuoteListViewModel.kt

class MyQuoteListViewModel: ViewModel() {    internal val selectItemEvent = SingleLiveEvent<Quote?>()}

SingleLiveEvent.kt, based on SingleLiveEvent.

class SingleLiveEvent<T> : MutableLiveData<T>() {    private val pending = AtomicBoolean(false)        @MainThread    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {        if (hasActiveObservers()) {            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")        }        // Observe the internal MutableLiveData        super.observe(owner, Observer<T> { t ->            if (pending.compareAndSet(true, false)) {                observer.onChanged(t)            }        })    }    @MainThread    override fun setValue(t: T?) {        pending.set(true)        super.setValue(t)    }    /**     * Used for cases where T is Void, to make calls cleaner.     */    @MainThread    fun call() {        value = null    }    companion object {        private const val TAG = "SingleLiveEvent"    }}

NOTE: The disadvantage of SingleLiveEvent is that there can only be one observer. There is solution to support multiple observers, which I haven't tried yet.

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.