checked checkbox become unchecked in recyclerview after scrolling - android

I have a recyclerview with a list of items , when i check an item and scroll until the checked checkbox disppear , and scroll again to it , the checkbox become unchecked
this is my adapter code
class ContactsAdapter(var list:ArrayList<contact>, private val listener: (contact) -> Unit):RecyclerView.Adapter<ContactsAdapter.viewHolder>() {
inner class viewHolder(view:View):RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): viewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.item_ui, parent, false)
return viewHolder(view)
}
override fun onBindViewHolder(holder: viewHolder, position: Int) {
var itemPosition = list[position]
holder.itemView.apply {
contact_name.text=itemPosition.contactName
contact_number.text=itemPosition.contactNumber[0]
check_number.setOnClickListener {
listener(itemPosition)
}
check_number.isChecked = itemPosition.checked
}
}
override fun getItemCount(): Int {
return list.size
}
}

A RecyclerView recycles its rows. Your setOnClickListener() needs to do something to hold onto which items are checked and unchecked, and your onBindViewHolder() needs to update the CheckBox when it binds an item. It seems like the second part is implemented, but perhaps not the first part. You may need to update itemPosition.checked in your lambda for setOnClickListener().

Related

Scroll to Expanded RecyclerView Item

I'm trying to scroll to a recycler view item if the item is expanded and/or clicked. I know that binding.recyclerview.layoutManager.scrollToPosition() can achieve this but I can't call the recycler view in the adapter class. Can I pass a boolean and position then say its true when the card item is clicked, then pass that to the main activity somehow..? I'm still very much a beginner so steps to do achieve this with best practices would really help me out. :)
EDIT*
So we got the above issue figured out. Now I am having a new issue where the item is scrolled to when clicked, but the expanded description text that appears after clicking is not scrolled into view. Lol ugh. Would really appreciate any help with this.
Hope you are having a great day!
class MainAdapter: RecyclerView.Adapter<MainViewHolder>() {
var movies = mutableListOf<Model>()
#SuppressLint("NotifyDataSetChanged")
fun setMovieList(movies: List<Model>) {
this.movies = movies.toMutableList()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterMovieBinding.inflate(inflater, parent, false)
return MainViewHolder(binding)
}
#SuppressLint("NotifyDataSetChanged")
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val movie = movies[position]
holder.binding.name.text = movie.name
holder.binding.category.text = movie.category
holder.binding.description.text = movie.desc
Glide.with(holder.itemView.context).load(movie.imageUrl).into(holder.binding.imageview)
holder.binding.expandedView.visibility = if (movie.expand) View.VISIBLE else View.GONE
holder.binding.cardLayout.setOnClickListener {
movie.expand = !movie.expand
// scroll to expanded item
notifyDataSetChanged()
}
}
override fun getItemCount(): Int {
return movies.size
}
}
class MainViewHolder(val binding: AdapterMovieBinding) :
RecyclerView.ViewHolder(binding.root)

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.

how to find out first item has changed in paging data adapter

I'm using paging 3 to paginate my items and then show them in recycler view. but there's a time that the first item change. for example, consider the situation below:
item A
item B
item C
after a while, for some reason, data gets update and list order changes to like bellow:
item B
item A
item C
my problem is:
recycler view stays exactly in its last position. I mean when update apply, item B will go to the first position but recycler view won't scroll to the top, it just states pointing to item A, and the user won't notify that item B has changed,
how can I set that recycler view scroll automatically just when the first item changed?
here is part of my adapter:
class TicketListAdapter : PagingDataAdapter<TicketListModel, TicketListAdapter.ViewHolder>(
diffCallback = object : DiffUtil.ItemCallback<TicketListModel>() {
override fun areItemsTheSame(oldItem: TicketListModel, newItem: TicketListModel): Boolean {
return oldItem.ticket_id == newItem.ticket_id
}
override fun areContentsTheSame(
oldItem: TicketListModel,
newItem: TicketListModel
): Boolean {
return oldItem == newItem
}
}
){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.ticket_list_item_model, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int)= holder.bind(getItem(position))
and here shows how I use my recycler view and data:
binding.recyclerView.apply {
this.adapter = adapter
this.layoutManager = LinearLayoutManager(requireContext())
}
viewModel.tickets.observe(viewLifecycleOwner, {
viewLifecycleOwner.lifecycleScope.launch {
adapter.submitData(it)
}
})
I also try that set observer but it didn't work:
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
if (adapter.getItemId(0) != firstItemId) {
binding.recyclerView.smoothScrollToPosition(0)
firstItemId = adapter.getItemId(0)
}
}
the firstItemId is initialized with value -1;
Calling adapter.notifyDataSetChanged() will make the recyclerview to reload, then your items should adjust.
Regarding scrolling (dublicate), have a look here: Scroll RecyclerView to show selected item on top

RecyclerView adapter `onCreateViewHolder` & `onBindViewHolder` invoked only once

I have a RecyclerView.Adapter like this:
internal class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private val data: List<MyModel> = SeedData().seed()
override fun onCreateViewHolder(v: ViewGroup, viewType: Int): MyViewHolder {
val binding = MyListitemBinding.inflate(LayoutInflater.from(v.context), v, false)
return MyViewHolder(binding)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(data[position])
}
}
However, only the first item from the data is getting displayed (i.e. onCreateViewHolder & onBindViewHolder invoked only one time). How can I make it display all items from the data properly?
The commented answers above are correct. My list item (views) were full height of screen:
This means that RecyclerView would only update the ViewHolder once you scroll to the next element. The solution is to modify the height of these items.

RecyclerView notifyItemChanged call onBindViewHolder also on not visible cells

I have a vertical RecyclerView with LinearLayoutManager with different item types and one ViewHolder. It is a messaging screen.
I want show/hide message date on click on the cell.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = R.layout.xxx
val viewHolder = ViewHolder(LayoutInflater.from(parent.context).inflate(layout, parent, false))
viewHolder.itemView.layout?.setOnClickListener {
setMessageDateVisibility(viewHolder)
}
return viewHolder
}
So i added the method to reload view with notifyItemChanged(pos)
private fun setMessageDateVisibility(holder: ViewHolder) {
val pos = holder.adapterPosition
if (pos.toLong() == THREAD_DEFAULT_POSITION)
return
val message = mDataset[pos]
message.isDateVisible = !message.isDateVisible
notifyItemChanged(pos)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = mDataset[position]
itemView.message_date.text = message.date
itemView.message_date.visibility = if (message.isDateVisible)
View.VISIBLE
else
View.GONE
}
But the problem a the end, is that because of this line notifyItemChanged(pos), the override onBindViewHolder method called for all not visible cells and so my screen is lagging with 150/200 cells
Can you help me to find a solution ?

Categories

Resources