Recyclerview illegal state exception with data binding and viewModel - android

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

Related

visit ViewModel in Recycler´s Adapter

i am stuck in the problem that, i want to visit my ViewModel in a RecyclerView´s Adapter, but i have the Exception:
"Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call"
class FoodInSortAdapter( val activity:MainActivity,private val list:List<String>):RecyclerView.Adapter<FoodInSortAdapter.Holder>() {
val viewModel :FoodViewModel by lazy {
println("servus")
ViewModelProvider(activity).get(FoodViewModel::class.java)}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.sortitem, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.sortName.text = list[position]
holder.itemView.setOnClickListener {
val m=list[position]
viewModel.setQuery(m)
}
}
anyone know what ´s wrong?
thanks
First things first, you should not initialize viewModel outside of activity or fragment.
I assume you do not create adapter in onCreate but during initialization phase. There are two solution for your concern.
Initialize vm in activity, and set it in adapter by newly added method setupViewModel, called in onCreate. That's the better one.
class FooActivity : Activity{
private val viewModel: FoodViewModel by lazy {
println("servus")
ViewModelProvider(activity).get(FoodViewModel::class.java)
}
private val adapter = FoodInSortAdapter(emptyList())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter.setupViewModel(viewModel)
}
}
class FoodInSortAdapter(private val list:List<String>):RecyclerView.Adapter<FoodInSortAdapter.Holder>() {
private lateinit var viewModel: FoodViewModel
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.sortitem, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.sortName.text = list[position]
holder.itemView.setOnClickListener {
val m=list[position]
viewModel.setQuery(m)
}
}
fun setupViewModel(vm: FoodViewModel){
viewModel = vm
}
}
Create adapter in onCreate method, that should also work for you.
Cheers

lateinit property binding has not been initialized

I know that there are similar questions to this, but I just cant find something that is similar, I've been studying new things to learn, and while converting kotlin synthetics to viewvbinding mode, I've encountered this error
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:60)
at com.codepalace.chatbot.ui.MessagingAdapter.onBindViewHolder(MessagingAdapter.kt:17)
It says that I have to initialize the binding, but I dont know where to put it.
This is the code.
class MessagingAdapter: RecyclerView.Adapter<MessagingAdapter.MessageViewHolder>() {
var messagesList = mutableListOf<Message>()
private lateinit var binding: MessageItemBinding
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return MessageViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false)
)
}
override fun getItemCount(): Int {
return messagesList.size
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val currentMessage = messagesList[position]
when (currentMessage.id) {
SEND_ID -> {
holder.itemView.findViewById<View>(R.id.tv_message).apply {
binding.tvMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_bot_message).visibility = View.GONE
}
RECEIVE_ID -> {
holder.itemView.findViewById<View>(R.id.tv_bot_message).apply {
binding.tvBotMessage.text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.findViewById<View>(R.id.tv_message).visibility = View.GONE
}
}
}
fun insertMessage(message: Message) {
this.messagesList.add(message)
notifyItemInserted(messagesList.size)
}
private lateinit var binding: MessageItemBinding
You didn't initialize the binding object, as you defined it as lateinit, then you should define it.
This is typically in onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackedActivityHolder {
val inflater = LayoutInflater.from(parent.context)
binding = DataBindingUtil.inflate<MessageItemBinding>(inflater, R.layout.message_item, parent, false)
return MessageViewHolder(
binding
)
}
UPDATE
You need to accept MessageItemBinding type instead of View in the MessageViewHolder constructor, and use itemView.root to get the root view of the list item.
inner class MessageViewHolder(itemView: MessageItemBinding) : RecyclerView.ViewHolder(itemView.root) {
init {
itemView.root.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
The reason why You get this error is that MessageItemBinding should not be in Adapter but in ViewHolder class. You can make RecyclerView like this:
object MessageDiffCallback : DiffUtil.ItemCallback<Message>()
{
override fun areItemsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: Message,
newItem: Message
): Boolean =
oldItem == newItem
}
I assume that Message is a data class. You have to create this MessageDiffCallback and override these 2 methods in a similar way that I did it.
Now Create ViewHolder class:
class MessageViewHolder private constructor(
private val binding: MessageItemBinding
) : RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun create(parent: ViewGroup): MessageViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = MessageItemBinding.inflate(layoutInflater, parent, false)
return MessageViewHolder(
binding
)
}
}
fun bind(
message: Messaage
)
{
// Here You can do everything.
// You pass the message and based on this You can set up a view and use binding
}
}
And now Adapter class
class MessageAdapter : ListAdapter<Message, MessageViewHolder>(MessageDiffCallback)
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder =
MessageViewHolder.create(parent)
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) =
holder.bind(
message = getItem(position),
)
}
Now You should have a working adapter. To put data in it use messageAdapter.submitList(messages) in Your Fragment/Activity
Maybe not the best answer because it changes Your code and uses a little different logic but it should work better. You can check google sample code here or take codelab here. It is free after signing up

android - Creating and using ViewBinder inside a RecyclerView adapter

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])
}

Implementing ListAdapter for a RecyclerView Fragment

I've done this successfully with a normal ViewAdapter but I can't seem to get it working with a ListAdapter.
Here is my Fragment that does most of the work:
class CrimeListFragment: Fragment() {
//Required interface for hosting activities
interface Callbacks {
fun onCrimeSelected(crimeId: UUID)
}
private var callbacks: Callbacks? = null
private lateinit var crimeRecyclerView: RecyclerView
private val crimeListViewModel: CrimeListViewModel by lazy {
ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
}
override fun onAttach(context: Context) {
super.onAttach(context)
callbacks = context as Callbacks?
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_crime_list, container, false)
crimeRecyclerView =
view.findViewById(R.id.crime_recycler_view) as RecyclerView
crimeRecyclerView.layoutManager = LinearLayoutManager(context)
crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
crimeListViewModel.crimeListLiveData.observe(
viewLifecycleOwner,
Observer { crimes ->
crimes?.let {
Log.i(TAG, "Got crimes ${crimes.size}")
updateUI(crimes)
}
}
)
}
override fun onDetach() {
super.onDetach()
callbacks = null
}
private fun updateUI(crimes: List<Crime>) {
crimeRecyclerView.adapter = CrimeListAdapter(crimes)
}
companion object {
fun newInstance(): CrimeListFragment {
return CrimeListFragment()
}
}
private inner class CrimeHolder(view: View)
: RecyclerView.ViewHolder(view), View.OnClickListener {
private lateinit var crime: Crime
private val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
private val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
private val solvedImageView = itemView.findViewById<ImageView>(R.id.crime_solved)
init {
itemView.setOnClickListener(this)
}
fun bind(crime: Crime) {
this.crime = crime
titleTextView.text = crime.title
dateTextView.text = crime.date.toString()
solvedImageView.visibility = if(crime.isSolved) {
View.VISIBLE
} else {
View.GONE
}
}
override fun onClick(v: View) {
callbacks?.onCrimeSelected(crime.id)
}
}
private inner class CrimeListAdapter(var crimes: List<Crime>)
: ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view =
layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(crimes[position])
}
}
private inner class DiffCallback: DiffUtil.ItemCallback<Crime>() {
override fun areItemsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem == newItem
}
}
}
And here is the fragment's viewmodel:
class CrimeListViewModel: ViewModel() {
private val crimeRepository = CrimeRepository.get()
val crimeListLiveData = crimeRepository.getCrimes() //returns LiveData<List<Crime>>
}
Android documentation has this regarding ListAdapter:
While using a LiveData is an easy way to provide data to the adapter, it isn't required - you can use submitList(List) when new lists are available.
I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But crimeRecyclerView.adapter has no .submitList() function. So how do I pass on the new list?
LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I observe this time? Or do I just add code to my existing Observer?
Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's functions get called. I'm not sure if this is a real problem or just the consequence of the above.
1.I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But
crimeRecyclerView.adapter has no .submitList() function. So how do I
pass on the new list?
crimeRecyclerView.adapter return RecyclerView.Adapter type
submitList() is a method of ListAdapter, a sub-class of RecyclerView.Adapter
You need to cast from super to sub class before calling that method, like this.
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
2.LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I
observe this time? Or do I just add code to my existing Observer?
Your code for this part is good, no need to do more.
3.Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's
functions get called. I'm not sure if this is a real problem or just
the consequence of the above.
The best part of using ListAdapter is you do not need to provide a list of data (crimes in your case) to constructor.
Back to your code, you need to change 3 things.
// crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
crimeRecyclerView.adapter = CrimeListAdapter()
and
// crimeRecyclerView.adapter = CrimeListAdapter(crimes)
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
and
//private inner class CrimeListAdapter(var crimes: List<Crime>) :
// ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
//
// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
// val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
// return CrimeHolder(view)
// }
//
// override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
// holder.bind(crimes[position])
// }
//}
private inner class CrimeListAdapter : ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(getItem(position))
}
}

Recycleview in Kotlin

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
}
}

Categories

Resources