viewholder init for onItemClick is not working - android

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.

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.

Problem with Kotlin Generics in a ListAdapter

I am trying to right a Generic ListAdapter for RecyclerView. I have 3 things I want to pass into the Adapter. The List of items, the row layout to use and the ViewHolder. I am able to get the list generically and the layout, but its the ViewHolder. Here is what I have so far but I am still new to Generics in Kotlin. I tried using the Class out method and then I am having issues with calling the constructor for a specific viewholder.
abstract class AbstractListAdapter(
private val items: List<*>,
private val layoutId: Int,
private val viewHolderClass: ???? >
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return viewHolderClass.??? // need to call the constructor of the specific viewholder passed in
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun getItemCount(): Int {
return items.size
}
}
// List adapter uses abstract
class ListAdapter(private items: List<Files>, private val id: Int, private viewHolder: FileViewHolder) : AbstractListAdapter(items, id, fileViewHolder) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
... some code here for the viewholder and list combining
}
}
// Specific VH that want to pass into abstract generic yet it will call the constructor from there.
class FileViewHolder(fileView: View) {
.... grab views for specific layout
}
You maybe do this by taking a ViewHolder constructor instead of class. That way you won't have to use reflection to instantiate the ViewHolder. You need to have a generic type for the ViewHolder, so your subclasses can properly implement onBindViewHolder and have access to the specific type of ViewHolder.
Also, you must make the items property have a type, or you won't be able to use it. And your subclass probably needs to be able to access it, so it needs to be protected, not private.
I did not test this:
abstract class AbstractListAdapter<VH: RecyclerView.ViewHolder> (
protected val items: List<*>,
private val layoutId: Int,
private val viewHolderConstructor: (View) -> VH >
) : RecyclerView.Adapter<VH>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return viewHolderConstructor(v)
}
//...
}
Then to implement it, you specify the ViewHolder constructor in the superconstructor call and you specify the type. Since the type is specified, you don't need a constructor parameter for it in the subclass.
class ListAdapter(private items: List<Files>, private val id: Int) : AbstractListAdapter<FileViewHolder>(items, id, ::FileViewHolder) {
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
//...
}
}
That said, I'm not sure what this actually achieves for you. It just seems like moving code around. You will still have to extract view references from the parent view. You just have to do it in the ViewHolder initialization instead of in onCreateViewHolder. And now you will have to be careful to pass in the right layout that matches up with the specific ViewHolder type. You may as well remove that parameter and do the layout in the ViewHolder constructor to avoid that issue. But now all you've done is move the onCreateViewHolder functionality into your ViewHolder's init block.
Also, your version of the abstract class is subverting expected results of the functions you've overridden. Why would every item in the list have a different type? Why would the item ID's be based on list position? This just messes up the functionality for editing the list data (rearranging, adding and removing will be broken).

Interface between recyclerview adapter & viewholder

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

Cannot access var object of inner class in kotlin

I am trying to write a RecyclerView adapter class in Kotlin for android. I am trying to use the traditional way of creating a custom viewer class, for custom objects, and use a click listener in that. While I am able to do rest of the things like access the variables of inner class and show the RecyclerView, what I have not been able to do is add click listener to the var objects of inner class.
ie something like
var convertView : View? = itemView
convertView.setOnClickListener(this)
Following is my complete code of adapter class
public open class TestAdapter(val items: MutableList<Any>, val context: Activity) : RecyclerView.Adapter<TestAdapter.CustomViewHolder>() {
public var mItem: MutableList<Any> = items
public var mActivity: Activity = context
protected var clickListener: ExampleInterface? = null
public interface ExampleInterface {
fun click(pos: Int) {
}
}
open public fun setListener(mInterFaceListener: ExampleInterface) {
clickListener = mInterFaceListener
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
var parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
return CustomViewHolder(parentLayout)
// return CustomViewHolder(LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false))
}
override fun getItemCount(): Int {
return mItem.size
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onBindViewHolder(p0: CustomViewHolder, p1: Int) {
p0.dataView.text = mItem.get(p1).toString()
}
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
var mposition = adapterPosition
override fun onClick(p0: View?) {
if (clickListener != null) {
clickListener!!.click(mposition)
}
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}
So if you see these two lines in CustomView class
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
I cannot access these variables "convertView" and "dataView" so that I can set clicklistener to them. So how to achieve it ?
Thanks :)
I referred to this site
https://www.raywenderlich.com/367-android-recyclerview-tutorial-with-kotlin
Here I go to know my mistake , I need to use init in the class, there I am able to access it and initialize on click listener. Got it working
init {
convertView?.setOnClickListener(this)
}
Though the above answer could be acceptable as well, as I am new to Kotlin I cannot say which one is the better option, but my requirement is satisfied with the above mentioned site.
Thank you :)
In RecyclerView adapters, You could place the OnClickListeners in onCreateViewHolder in order to prevent them from being set each time onBindViewHolder is called (as onBindViewHolder is called multiple times when RecyclerView is scrolled). Use parentLayout in your onCreateViewHolder to access your views and set onClickListener to them. to determine current position in onCreateViewHolder you can do as below :
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
val parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
val customViewHolder = CustomViewHolder(parentLayout)
parentLayout.myExampleTextView.setOnClickListener {
// Place onClick logic here.
// If you need position, do as bellow :
val adapterPosition = customViewHolder.adapterPosition
if(adapterPosition != RecyclerView.NO_POSITION) {
// Place your position dependent logic here
}
}
return customViewHolder
}
UPDATE:
I updated the code snippet above and added the RecyclerView.NO_POSITION (which is equal to -1) check. The reason for the position being returned as -1 is explained below.
From android docs:
The other set of position related methods are in the form of
AdapterPosition. (e.g. getAdapterPosition(), findViewHolderForAdapterPosition(int)) You should use these methods
when you need to work with up-to-date adapter positions even if they
may not have been reflected to layout yet. For example, if you want to
access the item in the adapter on a ViewHolder click, you should use
getAdapterPosition(). Beware that these methods may not be able to
calculate adapter positions if notifyDataSetChanged() has been called
and new layout has not yet been calculated. For this reasons, you
should carefully handle NO_POSITION or null results from these
methods.
you can access them in the onBindViewHolder using the p0 this way p0.dataView so there you can set listeners successfully

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).

Categories

Resources