Interface between recyclerview adapter & viewholder - android

So I have a Recyclerview & multiple viewholders. Now one of my viewholder contains an item that has few animations. Now if a user clicks on the item activity opens and I have no control over the activity, the only way I got to know is via the callback in my fragment i.e onPause & onResume. I want to call animation.onResume() once my fragment onResume() is invoked.
Interface class
interface CustomTabClosedListener {
fun onCustomTabClosed()
}
Adapter Relevant code:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
lateinit var viewViewHolder: RecyclerView.ViewHolder
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater!!.inflate(R.layout.feed_coverage, parent, false)
viewViewHolder = CoverageViewHolder(view)
listener = viewViewHolder
}
where listener is private var listener: CustomTabClosedListener?=null
ViewHolder
class CoverageViewHolder(val view: View) :CustomTabClosedListener {
override fun onCustomTabClosed() {
animation?.resume()
Now my fragment calls this adapter method in onResume -
fun customTabClosed() {
listener?.onCustomTabClosed()
}
But here my listener is getting null and I don't know the reason why. How can i invoke my method in viewholder via my adapter/fragment? Is there any other better approach?

I guess the reason why you get null is because you should have only a single call back in the Adapter viewHolder, and all the animation should be managed by the view. Is better and clean if the adapter manage some really elementar business logic, leaving what should happen to the view into the view itself.
Let's say you have an xml file with your animation
you could have from the view the control to notify the adapter and start the animation
fun IamACoolCallBack(){
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);
with(recyclerView){
setLayoutAnimation(controller);
getAdapter().notifyDataSetChanged();
scheduleLayoutAnimation();
}
}
EDIT
I found this tutorial it looks pretty neat to me

Related

Android - RecyclerView - edittext notifyItemChanged keep focus and continue typing

I have a RecyclerView which has an EditText in its items.
For each EditText I have added a textChangeListener.
Whenever the user types a character I want to do an input validation and so I can change the item, for example show red border in case of wrong input etc...
After I do the validation I call:
adapter.notifyItemChanged(itemPosition)
to update the view.
But the problem is that the focus is lost from the EditText and I have to click again inside the box in order to continue typing.
Is there a solution for this case? How can I continue typing while the view is updated after calling notifyItemChanged ?
ADAPTER
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
binding.editText.addTextChangedListener {
// Do layout changes....
if (isInvalidInput) {
item.setError = true
....
...
}
adapter.notifyItemChanged(itemPosition)
}
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position])
}
inner class ItemViewHolder(val binding: LayoutItemBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel) {
// Setup view for item
}
}
}
Try to move your error setting code in the bind function of the ViewHolder and update the error status outside of the TextWatcher:
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position], position)
}
inner class ItemViewHolder(val binding: LayoutItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel, position: Int) {
binding.editText.addTextChangedListener {
// Update the ItemModel on text change
adapter.notifyItemChanged(position)
}
// Update the view error status everytime it is updated
val isInvalidInput = ... // Add your logic
if (isInvalidInput) {
model.setError = true
....
....
}
}
}
}
First, Please post your code.
If you called notifyDatasetChanged(), all views of recyclerview will refreshed (it means that the views could be recreated.).
So if you want remain focus in edittext, you not call notifyDatasetChanged() or set focus to your edittext after notifyDataChanged().
But setFocus is bit more difficult.
So I suggest method that update views of recyclerview without call notifyDatasetChanged().
I think you can directly update view of recyclerview without call notifyDataChanged().
You should save the id of item that contains the EditText that is editing. And in the bind() method of ViewHolder, you will check if the item is currently being edit, you will call requestFocus() method on EditText.
And please take note that when you call notifyDataChanged(), the RecycleView maybe check the data of each item to know whether it was changed or not. If the data of an item was changed, RecycleView will redraw that item.

viewholder init for onItemClick is not working

I am researching on how to add an onClick event on my recyclerview properly,
currently I am using the interface inside my customAdapter
class CategoryAdapter(val categoryList : List<CategoryObject>, val context: Context, val mItemClickListener: MainInterface) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
interface MainInterface {
fun onCategoryItemClick(categoryKey: Int)
}
override fun getItemCount(): Int {
return categoryList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.listview_category, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.categoryName?.text = categoryList[position].categoryName
// holder?.categoryName.setOnClickListener{
// mItemClickListener.onCategoryItemClick(position)
// }
// holder?.categoryName?.setOnClickListener { listener(categoryList[position]) }
}
inner class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val categoryName = view.lv_txt_category_name
init {
view.setOnClickListener {
mItemClickListener.onCategoryItemClick(adapterPosition)
}
}
}
}
viewHolder's onClick does not register on my activity ovveride function.
but putting the onClick inside onBindViewHolder works perfectly,
I'm not sure which is more efficient, if the onBindViewHolder onClick is the right answer, then I'll stick with it, but if the viewHolder is the right one, why it does not work properly?
Thanks in advance!
Update
This is the stackoverflow post I'm using to research things
RecyclerView itemClickListener in Kotlin
tried android:clickable="true" in your xml?
As the documentation suggest for:
RecyclerView.Adapter.html#onBindViewHolder
Called by RecyclerView to display the data at the specified position.
This method should update the contents of the itemView to reflect the
item at the given position.
And for the RecyclerView.Adapter.html#onCreateViewHolder
Called when RecyclerView needs a new RecyclerView.ViewHolder of the
given type to represent an item.
This new ViewHolder should be constructed with a new View that can
represent the items of the given type. You can either create a new
View manually or inflate it from an XML layout file.
As onCreateViewHolder is a method where we return the type of ViewHolder only while in onBindViewHolder we update or initialise the data to display.
It means no data is binded in onCreateViewHolder and binding anything in this method will have no effect.

Kotlin - Recycleview ViewHolder OnClick not working

I have an app in Kotlin done by another developer. It sets Onclick listeners in "onBindViewHolder" method of RecycleView Adapter. Somehow after the view scrolls out of the visible area (i.e. user scrolls down) and the user scrolls back to top, the onclick method only gets called after the view is tapped TWICE. Don't understand what's going on, and my Kotlin knowledge is very little. This is how the onclick listener is set:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//...
holder.itemView.editButton.onClick {
Log.d("Holder", "Click")
}
//...
}
It is working EXCEPT when the view gets out of screen. Also, onBindViewholder is not called for views just coming back from outside of the screen, which is not standard I believe?
There is no special settings for the recycleview either, just a simple linearlayout and a single view type.
Any ideas?
Try this code.
holder.itemView.editButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d("Holder", "Click")
}
})
Make sure you bind the value of edittext with ViewHolder class like below
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
// Holds the button that will add each editButton to
val editButton = view.editButton
}
After try this simple code
holder?.editButton.setOnClickListener {
Log.e("editbutton", "onClick")
}

Debugger doesn't stop at breakpoint in Recyclerview adapter

class testAdapter(
private val list: ArrayList<Objects>
) : RecyclerView.Adapter<testViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): testViewHolder {
*BREAKPOINT HERE*
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.X, parent, false)
val viewHolder = testViewHolder(view)
return viewHolder
}
override fun onBindViewHolder(holder: testViewHolder, position: Int) {
holder.bind()
}
override fun getItemCount(): Int { return contentList.size }
}
I can see that program was here, because breakpoint is check-marked. I'm pretty sure, that it's caused by async (by retrofit) call, which gathers item list, but how can I fight with this? I can write code, if I can't debug none of Adapters functions.
I set up adapter like this:
onCreate() {
recyclerView =rootView.findViewById(R.id.test)
gridLayoutManager = /* custom grid layout manager */ (this is fine)
recyclerView.layoutManager = gridLayoutManager
}
asyncFunctionResult(list: List) {
contentAdapter = testAdapter()
recyclerView.adapter = contentAdapter
}
Sorry for pseudo code, but this should be enough. Obviously, there are alot of things wrong in adapter ect., but it should atleast get a hit [stop] from debugger. Any ideas?
For anybody having this problem: try checking out if you forgot to add the recyclerview.layoutManager.
It looks like your list is not filled, onCreateViewHolder is only called when it needs to inflate an item which is when a listitem needs to be shown.
You could try to add an item to contentlist or list (as your code shows 2 lists, I assume this is also pseudocode).

Encapsulate ViewHolder Functionality for reuse

I have an application with a news feed. I am using a recycler view to populate my news feed. I have 10 variations of viewholders for the recycler view depending on what type of content received from the server.
That all works fine.
My problem is when the user clicks on the item in the recycler view the user is brought to a new fragment, with the content and all the comments below.
My issue is this is a new fragment, so I'm outside of the recycler view. In this case I have to inflate the layout of the item clicked on in the recyclerview and that's fine. However, it should have all the functionality the item in the feed as, so for example, play media (video, audio), navigate to new fragments.
My question, is it possible to someway encapsulate the functionality of the viewholder in the recyclerview? Otherwise the same functionality will be repeated.
Any help is appreciated.
I've attached a drawing to help illustrate.
class VideoViewHolder(
private binding: VideoItemBinding
) : RecyclerView.ViewHolder(binding.root){
companion object {
fun create(
inflater: LayoutInflater,
viewGroup: ViewGroup
): VideoViewHolder {
val binding = DataBindingUtil.inflate<VideoItemBinding>(
inflater, R.layout.item_video, viewGroup, false
)
return VideoViewHolder(binding)
}
}
fun bind(video: Video){
// bind view to video model
// set click listeners
// fire network request on click events and on response check if the viewholder is binded to same model else its has been reused.
}
}
From Video Adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return VideoViewHolder.create(inflater, parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is VideoViewHolder -> {
holder.bind(item as Video, doubtsRepo)
}
}
}
To reuse in activity/fragment/ container layout without adapter manually create viewholder and call its bind function
val vh = VideoViewHolder.create(inflater, parent)
// get view from vh.binding.root and manually add it to view container and then bind the model to the view to reuse view holder functionality without any changes.
vh.bind(video)

Categories

Resources