Can someone explain the logic on how to handle this matter:
I have a fragment that after a WebSocket call inflates 2 Recyclerviews.
Child Recyclerview is nested to Parent Recyclerview and the parent adapter calls the child adapter. I want to put an Interface for a click listener which handles the click in the Child Items in the Fragment.
Where should I put the interface and which class should implement it?
What you're trying to do has been done multiple times.
There are various approaches you can try, but in general, responsibilities would look something like this:
YourContext
(Fragment/Activity)RecyclerView
. YourAdapter
YourAdapter
.interface YourThingClickHandler {
fun onThingWasClicked(thing: Thing) // and any other thing you may need.
}
YourContext: YourThingClickHandler
or if you want, you can keep an anonymous/local instance of that. I usually do the former and then implement the fun onThingWasClicked(...)
in the fragment/activity, it depends what you need to do when the item was clicked. YourAdapter
Things
and one YourThingClickHandler
instance. So in your Fragment/Activity you'd do, something like (pseudo code):// This is called once your ViewModel/Presenter/Repository/etc. makes the data available.
fun onThingsLoaded(things: List<Thing>) {
adapter.setClickHandler(this) // this can be passed when you construct your adapter too via constructor like adapter = YourAdapter(this)
adapter.submitList(things) // if the adapter extends a `ListAdapter` this is all you need.
}
Now that you've passed an outer click handler, you need to deal with the inner list. Now you have a few choices:
1. pass the same click handler all the way in and let the innerAdapter
directly talk to this.
2. Have the outerAdapter
act as an intermediate between the clicks happening in the innerAdapter and bubble them up via this click handler you just supplied.
Which one you chose, will depend largely on what you want to do with it, and how you want to handle it. There's no right or wrong in my opinion.
Regardless of what you do, you still need to get from the view holder to this click handler...
So in YourAdapter
you should have another Interface:
interface InternalClickDelegate {
fun onItemTappedAt(position: Int)
}
This internal handler, will be used to talk from the viewHolder, back to your Adapter, and to bubble the tap up to the external click handler.
Now you can have a single instance of this, defined like so in your adapter class (remember this is Pseudo-Code):
private val internalClickHandler: InternalClickDelegate? = object : InternalClickDelegate {
override fun onItemTappedAt(position: Int) {
externalClickHandler?.run {
onThingWasClicked(getItem(position))
}
}
}
So if the external click handler (the YourThingClickHandler
you supplied) is not null, then fetch the item from the adapter's data source, and pass it along.
When you do onCreateViewHolder
, have a ViewHolder that takes... you guessed, a InternalClickDelegate
instance and so...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
// decide which viewHolder you're inflating... and...
return YourViewHolder(whateverViewYouInflate, internalClickHandler)
Now your ViewHolder(s) have a reference to this internal click handler...
so when you do onBindViewHolder(...)
you probably call a common ViewHolder method of your choice, for example if your View holder can be of different types, you probably have an Abstract viewHolder with a fun bind(thing: Thing)
method or similar that each concrete viewHolder subType will have to implement... in there, you'd do something like this:
override fun bind(thing: Thing) {
if (clickHandler != null) {
someViewYourViewHolderInflated.setOnClickListener(this) // this assumes your ViewHolder implements View.OnClickListener from the framework
}
}
Because your ViewHolder implements View.OnClickListener
, you must implement the onClick method in it:
override fun onClick(v: View?) {
clickHandler?.onItemTappedAt(adapterPosition)
}
And this is how your ViewHolder, will receive the tap/click event from Android in the onClick
method, if you supplied a click Handler (you did in the adapter onCreateViewHolder when you passed the internalClickHandler), it will simply bubble the tap, passing the position. adapterPosition
is the Kotlin equivalent of calling getAdapterPosition()
in a RecyclerView adapter.
itemView
or just any widgets you want to make clickable, if you want the whole cell to be clickable, simply use itemView
which is the "whole" view of the ViewHolder.When the viewHolder's view is tapped, android calls the click listener's onClick
method. In there, and because you are in a ViewHolder, you can do getAdapterPosition() and pass this to the internal
click handler you received.
The Adapter then can transform that position back into data, and because you supplied an External clickListener
, it can pass the actual item back to the external click listener.
There's nothing special about that, you simply need to provide the same mechanism, and keep passing things around. What you do or how many of these interfaces you have, depends entirely on what you're trying to achieve; like I said at the beginning, each solution is different and other factors must be taken into account when making architectural decisions.
In general, keep this thing in mind: Separation of Concerns: keep things small and to the point. For E.g.: it may seem crazy to have this double interface, but it's very clear what each does. The internal one, is simply concerned about a "tap" in a "view", and to provide the position in a list where said tap occurred.
This is "all" the adapter needs to fetch the data and make an informed guess at what item was truly tapped.
The fragment doesn't know (or care) about "positions", that's an Adapter's implementation detail; the fact that positions
exist, is oblivious to the Fragment; but the Fragment is happy, because it receives the Thing
in the callback, which is what most likely needs to know (if you needed to know the position for whatever reason, tailor and modify the externalCallback
to have the signature of your choice.
Now replicate the "passing hands" from your OuterAdapter to your InnerAdapter, and you have done what you wanted to do.
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With