Question: Handling clicks in RecyclerView Adapter
ThanosFisherman opened this issue · 7 comments
Hello,
I was wondering How can I handle clicks from a view inside a RecyclerView Adapter and later collect them outside this adapter for instance in a Fragment/Activity? I'm probably looking for something like PublishSubject
in RxJava
For instance
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiscoverHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.section_title, parent, false)
view.clicks().collect() //How can I somehow propagate those clicks to a property?
..............
}
I used to do this with callbacks but I want to achieve a similar thing using this library.
The equivalence of RxJava Subject
in Coroutines is Channel.
You could use a Channel
in your adapter to collect the view.clicks()
Flows emitted by each item, and expose it as a single itemClicks: Flow<MyItem>
for Fragment / Activity to consume:
class MyListAdapter(
private val coroutineScope: CoroutineScope // pass in the `lifecycleScope` from Activity / Fragment
) : ListAdapter<MyItem, MyViewHolder>(diffCallback) {
private val itemClicksChannel: Channel<MyItem> = Channel(Channel.UNLIMITED)
val itemClicks: Flow<MyItem> = itemClicksChannel.consumeAsFlow()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
...
return MyViewHolder(view, itemClicksChannel, coroutineScope)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
class MyViewHolder(
private val view: View,
private val itemClicksChannel: Channel<MyItem>,
private val coroutineScope: CoroutineScope
) : RecyclerView.ViewHolder(view) {
fun bind(myItem: MyItem) {
....
view.clicks().onEach {
clickChannel.offer(myItem)
}.launchIn(coroutineScope)
}
}
In Activity / Fragment:
...
myAdapter = MyListAdapter(lifecycleScope)
myAdapter.itemClicks.onEach { myItem ->
// handle clicked item
}.launchIn(lifecycleScope)
Thank you that's exactly what I needed.
Passing lifecycleScope
in MyListAdapter
, then re-emitting clicks in holder through channel and lastly converting the channel to Flow was something I never thought of until now.
I must admit the whole thing is a bit verbose compared to the equivalent of rxjava though.
Thanks again!
How would you do the same with RxJava? I’d imagine the RxJava equivalence basically uses a PublishSubject instead of channel, and exposed an Observable instead of Flow. Are there any other differences?
Yes exactly. Now that I'm thinking of it there is not much difference whatsoever. If anything, in RxJava you wouldn't collect (subscribe) the events from clicks() in order to offer them again. Instead you'd just do this
RxView.clicks(view)
.takeUntil(RxView.detaches(pParent))
.map(aVoid -> view)
.subscribe(mViewClickSubject);
where mViewClickSubject
is a PublishSubject
exposed as Observable
private PublishSubject<View> mViewClickSubject = PublishSubject.create();
public Observable<View> getViewClickedObservable() {
return mViewClickSubject.asObservable();
}
Which is exactly the same thing but maybe you save a few curly braces along the way I guess :)
Oh now I remember Subject
is both a Subscriber
and an ObservableSource
:)
You can implement a similar API with Flow with an extension function:
fun <T> Flow<T>.transferToChannel(channel: Channel<T>): Flow<T> = onEach {
channel.offer(it)
}
itemView.clicks().map { myItem }
.transferToChannel(itemClicksChannel)
.launchIn(coroutineScope)
Only saved a few characters though:)
It's good enough for me. I'm pretty new to coroutines and these little snippets help me better understand how things work :)
Cool. Let me know if you have more questions. Closing now.