onBindViewHolder not called when use data binding on Recyclerview.
So, the Activity show blank white screen.
When I see on logcat, the list size and itemCount is 20 but onBindViewHolder method never called.
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
private val listArticle = mutableListOf<Article>()
fun update(list: List<Article>) {
listArticle.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): NewsAdapter.ViewHolder {
return ViewHolder(viewGroup)
}
class ViewHolder(
private val parent: ViewGroup,
private val binding: AdapterNewsBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.adapter_news,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(article: Article) {
binding.article = article
}
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
Log.d("NewsAdapter", "onBindViewHolder")
viewHolder.bind(listArticle[position])
}
override fun getItemCount() = listArticle.size
}
How to call onBindViewHolder in Recyclerview with data binding?
So I can show the data on Activity.
Solved
add apply plugin: 'kotlin-kapt' in your app gradle
So, you can make static method like this on your adapter
companion object {
#BindingAdapter("items")
#JvmStatic
fun RecyclerView.bindItems(items: List<Article>?) {
val adapter = adapter as NewsAdapter
if (!items.isNullOrEmpty()) adapter.update(items)
}
}
Done
Related
I have a recycler view(Parent) and inside it, I have another recycler View (Child).
There are 2 operations in child recycler View which I want to get on Fragment Class and do some things dynamically.
Architecture: MVVM
Yes, you can achieve your desired behavior by following these steps:
I will use Lambda to refer to Higher Order Function.
Pass the Lambda function from Activity/Fragment -> Parent Adapter
Pass the Lambda function from Parent Adapter -> Child Adapter.
For example, this code shows how to get a callback from nested Recyclerview when a user clicks Error Item from child Recyclerview.
//In Activity/Fragment
private var errorClick: () -> Unit
parentAdapter.setErrorClick(errorClick)
//In Parent Adapter
private var errorClick: () -> Unit
childAdapter.setErrorClick(errorClick)
//In Child Adapter - Now use errorClick to callback methods to Activity/Fragment
private var errorClick: () -> Unit // Use IT!
Let me know if you have any questions. Thanks.
Here is an example for higher order function.
TextAdapter.kt class
class TextAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<TextAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemTextBinding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val adapter = InnerAdapter {
onClick.invoke(it)
}
binding.recyclerViewList.adapter = adapter
binding.recyclerViewList.setData(list)
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Inner adapter
class InnerAdapter(
val onClick: (String) -> Unit
): RecyclerView.Adapter<InnerAdapter.ViewHolder>() {
inner class ViewHolder(val binding: ItemText1Binding) :
RecyclerView.ViewHolder(binding.root)
private val list: ArrayList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder =
ViewHolder(
ItemText1Binding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
binding.tvTxt.text = list[position]
holder.itemView.setOnClickListener {
//change background color
onClick.invoke(list[position])
}
}
override fun getItemCount(): Int = list.size
fun setData(newList: ArrayList<String>) {
list.clear()
list.addAll(newList)
}
}
Now you can get the value of the item click on the setAdapter
Let's see how
The below function is called from the fragment class where the adapter is set
val adapter = TextAdapter {
showToast(it)
}
Iam trying without succes to add detailview (onclick listener) after click on recyclerview item.
Iam using viewbinding and thats new to me.
My code for the adapter:
class MyRecyclerAdapter(listitems: MyFragment) : RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder>() {
var items: ArrayList<ListItems> = ArrayList()
// class MyViewHolder(val binding: LayoutMyListBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutMyListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
class MyViewHolder(var LayoutMyListBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(appInfo: ListItem) {
binding.root.setOnClickListener {
println("clicked for detail")
}
}
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.listItem.text = items[position].titel
}
fun submitList(ItemsList: ArrayList<ListItem>) {
items = ListItems as ArrayList<ListItem>
}
}
How can i implement the click listener with viewbinding in the adapter ? i know i can do holder.binding.setonclicklistener. But this is a bad option.
I I solved it so set an click listener on the ViewHolder
The bind() never get called, so call it in onBindViewHolder
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.vacatureTitel.text = items[position].titel
holder.binding.vacatureWerkgever.text = items[position].werkgever.bedrijfsnaam + " gevestigd in " + items[position].werkgever.werkgever_plaatsnaam
holder.bind(items[position]) // <<< Change here
}
I solved it to put an clicklistener on the Viewholder.
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 fetching a list of categories from an api and setting them into recyclerview . Adapter code is written in viewModel class and is called by the fragment that is calling the api. Below are the methods for setting adapters.
fun getAdapter(
listener: OtherVideoCategoriesRecyclerAdapter.OtherVideoCategoriesRecyclerAdapterListener,context: Context
): OtherVideoCategoriesRecyclerAdapter {
if (categoriesRecyclerAdapter == null)
categoriesRecyclerAdapter = OtherVideoCategoriesRecyclerAdapter(listener,context)
return categoriesRecyclerAdapter as OtherVideoCategoriesRecyclerAdapter
}
fun setItems(categories: ArrayList<OtherCategoriesItem>) {
categoriesList = categories
categoriesRecyclerAdapter!!.setItems(categoriesList!!)
}
And this is how I call these methods from my fragment class.
otherVideoViewModel.setItems(it.first.data!!.otherCategories as ArrayList<OtherCategoriesItem>)
Set Adapter method
private fun setAdapter() {
otherVideosCategoriesBinding.recyclerView.layoutManager = LinearLayoutManager(activity)
otherVideosCategoriesBinding.recyclerView.itemAnimator = DefaultItemAnimator()
adapter = otherVideoViewModel.getAdapter(adapterListener,activity!!)
otherVideosCategoriesBinding.recyclerView.adapter = adapter
}
And this is the adapter class.
class OtherVideoCategoriesRecyclerAdapter(private val listener: OtherVideoCategoriesRecyclerAdapterListener,val context: Context): RecyclerView.Adapter<OtherVideoCategoriesRecyclerAdapter.ViewHolder>() {
var categories = ArrayList<OtherCategoriesItem>()
interface OtherVideoCategoriesRecyclerAdapterListener {
fun onItemClicked(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OtherVideoCategoriesRecyclerAdapter.ViewHolder {
val inflater=LayoutInflater.from(context)
val binding = ItemOtherVideoCategoryBinding.inflate(inflater, parent, false)
return OtherVideoCategoriesRecyclerAdapter.ViewHolder(binding)
}
override fun getItemCount(): Int {
return categories.size
}
override fun onBindViewHolder(holder: OtherVideoCategoriesRecyclerAdapter.ViewHolder, position: Int) {
val item = categories[position]
holder.bindViews(item)
}
class ViewHolder(private val binding: ItemOtherVideoCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bindViews(model: OtherCategoriesItem){
binding.model=model
binding.executePendingBindings()
}
}
fun setItems(categoriesList: ArrayList<OtherCategoriesItem>) {
categories = categoriesList
notifyDataSetChanged()
}
}
When I run this code, it crashes with following exception.
java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(..., boolean attachToRoot)
I have tried all the related answers to this error but none of them worked for my case as many of those answers doesn't included data binding.
Hey just changing your onCreateViewHolder a bit. Try this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OtherVideoCategoriesRecyclerAdapter.ViewHolder {
val inflater=LayoutInflater.from(context)
val binding:ItemOtherVideoCategoryBinding = DataBindingUtil.inflate(inflater,R.layout.your_layout_name, parent, false)
return OtherVideoCategoriesRecyclerAdapter.ViewHolder(binding)
}
I have a problem with:
RecyclerView: No adapter attached; skipping layout
I can't define adapter in OnCreate because the list is not ready.
How I can define adapter in OnCreate? or what is a possible solution for resolve my problem?
In onCreate I did:
adapter = MyAdapter(this#MyActivity)
adapter.data = ArrayList()
Then later I just set adapter.date = xxx
In my adapter I have:
class MyAdapter(val activity: MyActivity) :
RecyclerView.Adapter<MyAdapter.BodyViewHolder>() {
var data: MutableList<MyModel>? = null
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data?.size ?: 0
fun ViewGroup.inflate(#LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BodyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.my_layout, parent, false)
return BodyViewHolder(view)
}
override fun onBindViewHolder(holder: BodyViewHolder, position: Int) {
holder.bindValue(data?.get(position), activity)
}
class BodyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindValue(record: MyModel?, activity: MyActivity) {
record?.let {
itemView.mTextView.text = ....
}
}
}
}
Worth mentioning that the height of my recycler view is wrap_content
<android.support.v7.widget.RecyclerView
android:id="#+id/mRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
It's totally ok if you don't have data in onCreate. What you need to do is to define the adapter and bind it with your RecyclerView. Once you have data ready, add data to the list in adapter and notify it. Example as below
class MyAdapter: RecyclerView.Adapter<MyViewHolder>() {
private val data = mutableListOf<MyModel>()
override fun getItemCount() = data.count()
fun addData(data : MyModel) {
// add single data the list or call addAll() to add a group of data.
// just remember not to replace the variable
}
fun clearData() {
}
fun deleteData(id: Int) {
}
}
class adpCoba(val context: Context, val datalist:ArrayList<dataCoba>):RecyclerView.Adapter<adpCoba.MyViewHolder>(){
class MyViewHolder (itemView:View):RecyclerView.ViewHolder(itemView){
val text :TextView=itemView.findViewById(R.id.textcoba)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(context).inflate(R.layout.fetch_coba,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currntItem = datalist [position]
holder.text.text= currntItem.nama
}
override fun getItemCount(): Int {
return datalist.size
}
}