I'm trying to set some values to some dialog properties based on list item position and have just converted some code from Java to Kotlin, but for some reason, all the myList[position] instances within the click listener return this error:
'getter for position: Int' is deprecated. Deprecated in Java.
However for onBindViewHolder What alternative can be used to resolve this error?
class MyRVAdapter(private val myList: ArrayList<Item>) : RecyclerView.Adapter<MyRVAdapter.ViewHolder>() {
override fun getItemCount(): Int {
return myList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.myBtn.text = (myList[position].textBtnTitle)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.my_cv, parent, false)
return ViewHolder(v)
}
class ViewHolder (itemView : View):RecyclerView.ViewHolder(itemView) {
val myBtn = itemView.findViewById<Button>(R.id.btn_a)!!
init {
myBtn.setOnClickListener {
val builder = AlertDialog.Builder(myBtn.context)
builder.setTitle(myList[position].txtDialogTitle)
builder.setMessage(myList[position].txtDialogMessage)
builder.setPositiveButton(android.R.string.ok){ dialog, _ -> dialog.dismiss() }
val dialog: AlertDialog = builder.create()
dialog.show()
}
}
}
}
This method is deprecated. This method is confusing when adapters nest other adapters. If you are calling this in the context of an Adapter, you probably want to call getBindingAdapterPosition() or if you want the position as RecyclerView sees it, you should call getAbsoluteAdapterPosition().
[Source:https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.ViewHolder#getAdapterPosition()]
I'm assuming you're talking about the references to position inside your ViewHolder class.
Use adapterPosition there instead. The Java equivalent is getAdapterPosition(). If you were using getPosition() in Java, you may have had the deprecation warning suppressed.
itemSelectedForPreview(layoutPosition)
OR
itemSelectedForPreview(bindingAdapterPosition)
Related
In my app, there is an Activity which has a RecyclerView inside, which loads the list of options needed for that screen.
In the code below, i tried to implement a binder, which is needed because of the recent Android changes.
However, when i open the activity starts, the application crashes, throwing this error, linking the line with binding = ItemSettingsBinding.bind(binding.root):
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
What am i doing wrong? What's the correct way to implement a binder inside an adapter?
AdapterSettings.kt
class AdapterSettings(
var settingsList: List<DataItemSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<AdapterSettings.SettingsViewHolder>() {
private lateinit var binding: ItemSettingsBinding
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
binding = ItemSettingsBinding.bind(binding.root)
holder.itemView.apply {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
I believe you're missing your inflate in onCreateViewHolder:
// Pseudo-Code
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val binding = ItemSettingsBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SettingsViewHolder(binding)
}
Then you can make use of it.
Create the binding in onCreateViewHolder and pass the binding into the ViewHolder instead of the inflated View. Thus you create a binding for each created view and only need to do the apply stuff in the onBindViewHolder
Example:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
val binding = ItemSettingsBinding.bind(view)
return SettingsViewHolder(binding)
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.binding.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
Adapt your ViewHolder accordingly
There is indeed another way to ViewBind in an adapter.
First, we need to setup the ViewHolder in a different way:
inner class SettingsViewHolder(private val binding: ItemSettingsBinding):
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
With this, we created a binding value inside the brackets, so we are able to call the items of the actual view or layout trough binding.root
Inside the viewholder, we need to create a function used to bind our items. We can either bind like this:
fun bind(item: Item) {
binding.item = item
binding.executePendingBindings()
}
Or like this:
fun bind(item: DataItemSettings) {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
NOTICE: 'getter for position: Int' is deprecated. Deprecated in Java.
And, final step, we need to write this, inside bindViewHolder:
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.bind(settingsList[position])
}
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).
I am trying to pass an array from my Recyclerview Activity to its Adapter as such:
//Setting NavBar Title
val navBarTitle = intent.getStringExtra(FirstCustomViewHolder.LESSON_TITLE_KEY)
supportActionBar?.title = navBarTitle
var content : Array<String>
if (navBarTitle == "Introduction"){
content = arrayOf("Intro1", "Intro2")
}
else{
content = arrayOf(":esson1-1", "Lesson 1-2")
}
I am passing the array as such:
recyclerView_main.adapter = SecondAdapter(content)
And I am getting an angry red underline as shown below.
On mouse-over the pop-up error reads:
Too many arguments for public constructor......
Is there a proper way to pass an array or variable to my adapter? I am fairly new to Kotlin and appreciate and pointers.
Thank you.
Edit:
As requested, this is my adapter class:
class SecondAdapter : RecyclerView.Adapter<SecondCustomViewGolder>(){
//Variable below to be replaced by array from Activity
var lessons = arrayOf("Satu", "Dua", "Tiga", "Empat", "Lima", "Enam", "Tujuh",
"Lapan", "Sembilan")
override fun getItemCount(): Int {
return lessons.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondCustomViewGolder {
var layoutInflater = LayoutInflater.from(parent.context)
var cellForRow = layoutInflater.inflate(R.layout.lesson_row, parent, false)
return SecondCustomViewGolder(cellForRow)
}
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
}
}
class SecondCustomViewGolder(var viewTwo : View) : RecyclerView.ViewHolder(viewTwo){
}
Does your SecondAdapter class constructor accept an Array as an argument? If not, you must add it there. The error is because you're trying to pass an argument to a constructor that accepts no arguments.
EDIT
Do it like so:
class SecondAdapter(val lessonArray: Array<String>) : RecyclerView.Adapter<SecondCustomViewGolder>(){
override fun getItemCount(): Int {
return lessons.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondCustomViewGolder {
var layoutInflater = LayoutInflater.from(parent.context)
var cellForRow = layoutInflater.inflate(R.layout.lesson_row, parent, false)
return SecondCustomViewGolder(cellForRow)
}
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
}
}
class SecondCustomViewGolder(var viewTwo : View) : RecyclerView.ViewHolder(viewTwo){
}
I made it a val since it's my preference. If you intend to modify the variable, than you just declare it as a var in the constructor. There's no need to assign it inside the class. Just declaring it in the constructor makes it accessible throughout the class.
You can use the ListAdapter
and use submitList()
I want to set a listener on RecyclerView items using RxJava2. The items are checkboxes. I want to listen to each item separately.
So I am getting an error Constructor of inner class ViewHolder can be called only with receiver of containing class in the
return TraceAdapter.ViewHolder(view)
class TraceAdapter(private var checkList: List<TraceViewModelRow> = listOf()) :
RecyclerView.Adapter<TraceAdapter.ViewHolder>() {
private val publishSubject = PublishSubject.create<Event>()
val events: Observable<Event> = publishSubject
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TraceAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_trace_task, parent, false)
return TraceAdapter.ViewHolder(view)
}
override fun getItemCount(): Int = checkList.size
override fun onBindViewHolder(holder: TraceAdapter.ViewHolder, position: Int) {
holder.bindTraceList(checkList[position])
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
}
inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bindTraceList(trace: TraceViewModelRow) {
with(trace) {
checkbox_itemText.clicks()
.map { checkList[layoutPosition] }
.subscribe { publishSubject }
checkbox_itemText.text = description
checkbox_itemText.isChecked = isChecked
}
}
}
}
I think you just need to change this line:
return TraceAdapter.ViewHolder(view)
to this:
return ViewHolder(view)
As you've explicitly marked it as an inner class, it requires an instance of the outer class, which you get by default because you're constructing it from within the outer class. However only if you don't prefix it with the outer class type, as shown above.
The distinction between inner and nested classes is a bit different in Kotlin and Java (Java uses the term static for nested classes that don't have an instance of the outer class). It's explained in more detail here.
I define mSelectedItem as a public var in the class CustomAdapter, I think mSelectedItem=getAdapterPosition() will be Ok when I use mSelectedItem in inner class ViewHolder.
But it failed, and display "Unresolved reference: mSelectedItem" error, why?
And more, what is good way for getAdapterPosition() in Kotlin, there is hint which display "This inspection reports calls to java get and set methods that can be replaced with use of Kotlin synthetic properties", but it will cause errro when I use mSelectedItem=getAdapterPosition .
class CustomAdapter (val backupItemList: List<MSetting>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
public var mSelectedItem = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.item_recyclerview, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: CustomAdapter.ViewHolder, position: Int) {
holder.bindItems(backupItemList[position])
holder.itemView.radioButton.setChecked(position == mSelectedItem);
}
override fun getItemCount(): Int {
return backupItemList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(aMSetting: MSetting) {
itemView.radioButton.tag=aMSetting._id
itemView.textViewUsername.text=aMSetting.createdDate.toString()
itemView.textViewAddress.text=aMSetting.description
mSelectedItem=getAdapterPosition() //It will cause error
}
}
}
If you don't want to make the ViewHolder an inner class (which you should not), you could create a class like AdapterSelection that has a field var selectedItem:Int inside it and replace your public var mSelectedItem = -1 with private var mSelectedItem = AdapterSelection(-1). Then pass the mSelectedItem to the bind method (bindItems(aMSetting: MSetting, adapterSelection:AdapterSelection) and inside the bind, set the position adapterSelection.selectedItem = getAdapterPosition().
You could have passed the adapter itself, but it is messy, that's why I suggest making another class.
ViewHolder is the Recycler rather than the operator.if you want to get the position,you put this mSelectedItem = position in onBindViewHolder.And this method named getAdapterPosition() always works on with notifyItemsetChanged().hope this will help you.