There are many ways to do this, but my favourite way is using LiveData
due to the loose coupling.
- We create a
ViewModel
to keepval selectItemEvent = SingleLiveEvent<Quote?>()
SingleLiveEvent
is actually a modifiedMutableLiveData
to allow single consumption/observe only. The reason is to avoid double consumption/observe during configuration change/screen rotation.ViewModel
is passed intoRecyclerView.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.