How to show all checkboxes in recyclerview? - android

I want to display all the checkboxes by long press, but only one is displayed
There is a part of my adapter
override fun onBindViewHolder(holder: ViewHoldder, position: Int) {
holder.textView?.text = lstWords[position].engWord
holder.textView2?.text = lstWords[position].transWord
holder.textView3?.text = lstWords[position].rusWord
holder.checkBox?.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
val word = Words(
lstWords[position].idWord!!,
lstWords[position].engWord!!,
lstWords[position].transWord!!,
lstWords[position].rusWord!!,
lstWords[position].check!!
)
if (holder.checkBox?.isChecked!!){
words.add(word.idWord.toString())
Log.d("MYTAG", "$words")
}
if (!holder.checkBox?.isChecked!!){
words.remove(word.idWord.toString())
Log.d("MYTAG", "$words")
}
}
})
holder.itemView.setOnLongClickListener {
holder.checkBox?.visibility = CheckBox.VISIBLE
true
}
}
How to change all checkboxes?

Use a property in the adapter class that determines if check boxes should be shown. Call notifyDataSetChanged() to force all item views to be rebound, so it will have a chance to modify each of them.
private var shouldShowCheckBoxes = false
//...
override fun onBindViewHolder(holder: ViewHoldder, position: Int) {
//...
holder.checkBox?.visiblility = if (shouldShowCheckBoxes) View.VISIBLE else View.INVISIBLE
holder.itemView.setOnLongClickListener {
shouldShowCheckBoxes = true
notifyDataSetChanged()
true
}
}

Related

If I check value once it store in array but if I uncheck it it doesnot remove

`Here problem is I have make an array where checked list items will be store and it works properly but when I uncheck it it doesn't remove from list and I am not figuring out why it is happening. Here isSelected is false by default
This is Adapter
private var empList = mutableListOf<Data>()
private val checkedList = SparseBooleanArray()
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val getEmployee = empList[position]
holder.binding.checkbox.isChecked = checkedList.get(position,false)
holder.binding.checkbox.setOnClickListener {
if (!checkedList.get(position,false)){
holder.binding.checkbox.isChecked = true
checkedList.put(position,true)
}
else
{
holder.binding.checkbox.isChecked =false
checkedList.put(position,false)
}
selectedList.onItemClickListner(getEmployee)
}
This is Main Fragment
override fun onItemClickListner(employees: Data) {
if (!employees.isSelected) {
list.add(employees)
Log.d("Remove", "onItemClickListner: $list ")
} else {
list.remove(employees)
}
Log.d("LIST", "onItemClickListner: $list")
}
}

position of recyclerview item changes after opening the fragment again

I am using android kotlin. i am working on online shopping app.
in the shopping cart section. all things works fine.adding an item to cart or delete the item or adding quantity and undoing the delete .
when i click on undo button the item is placed in its own position after undoing the deleted item. but when i close the fragment and open it again all item position changes and the item that i clicked first will be first item and so to the end
this is my recycler adapter:
class BasketRecyclerAdapter(val list: MutableList<Model.BasketItem>, val listener: ButtonListener) :
RecyclerView.Adapter<BasketRecyclerAdapter.ViewHolder>() {
lateinit var context: Context
lateinit var localStore: LocalStore // i saved user phone and pass in a shared prefs using this class
inner class ViewHolder(val binding: BasketItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BasketRecyclerAdapter.ViewHolder {
val binding: BasketItemLayoutBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.basket_item_layout,
parent,
false
)
localStore = LocalStore(parent.context)
context = parent.context
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: BasketRecyclerAdapter.ViewHolder, position: Int) {
val currentItem = list[position]
holder.binding.apply {
title.text = currentItem.title
numberPicker.progress = currentItem.quantity
color.setCardBackgroundColor(android.graphics.Color.parseColor(currentItem.color))
colorName.text = currentItem.color_name
price.text =
doubleToStringNoDecimal((currentItem.price * currentItem.quantity).toDouble())
Glide.with(context)
.asBitmap()
.load(currentItem.url)
.into(productImage)
holder.binding.apply {
numberPicker.doOnProgressChanged { numberPicker, progress, formUser ->
listener.onPickerProgressChanged(
numberPicker,
progress,
formUser,
position
)
price.text =
doubleToStringNoDecimal((currentItem.price * progress).toDouble())
}
delete.setOnClickListener {
listener.delete(holder.adapterPosition)
removeItem(holder.adapterPosition, holder)
}
}
}
}
override fun getItemCount(): Int = list.size
fun doubleToStringNoDecimal(d: Double): String? {
val formatter: DecimalFormat = NumberFormat.getInstance(Locale.US) as DecimalFormat
formatter.applyPattern("#,###,###,###")
return formatter.format(d)
}
fun removeItem(position: Int, viewHolder: RecyclerView.ViewHolder) {
val removedItem = list[position]
val removedPosition = position
list.removeAt(position)
notifyItemRemoved(position)
val snackbar = Snackbar.make(
viewHolder.itemView,
"این کالا از سبد خرید حذف شد",
Snackbar.LENGTH_LONG
)
snackbar.setAction("بازگرداندن") {
list.add(removedPosition, removedItem)
notifyItemInserted(removedPosition)
listener.SnackActionClicked(position)
}
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
interface ButtonListener {
fun onPickerProgressChanged(
numberPicker: NumberPicker,
progress: Int,
fromUser: Boolean,
position: Int
)
fun delete(position: Int)
fun SnackActionClicked(position: Int)
}
}
adapter =
BasketRecyclerAdapter(
items as ArrayList<Model.BasketItem>,
object : BasketRecyclerAdapter.ButtonListener {
override fun onPickerProgressChanged(
numberPicker: NumberPicker,
progress: Int,
fromUser: Boolean,
position: Int
) {
val currentitem = items[position]
viewModel.AddToCart(
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
progress,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
updateWorkerData(
currentitem.title,
currentitem.color,
Model.BasketItem::quantity,
progress
)
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
override fun delete(position: Int) {
val currentitem = items[position]
viewModel.DeleteItemInCart(
localStore.getMobile().toString(),
currentitem.title,
currentitem.color_name
)
itemList.remove(
Model.BasketItem(
currentitem.id,
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
currentitem.quantity,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
)
if (items.size == 1)
if (isLastVisible()) {
basketpic.visibility = View.VISIBLE
emptyBasketText.visibility = View.VISIBLE
} else {
basketpic.visibility = View.GONE
emptyBasketText.visibility = View.GONE
}
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
override fun SnackActionClicked(position: Int) {
val currentitem = items[position]
viewModel.AddToCart(
localStore.getMobile().toString(),
currentitem.category,
currentitem.title,
currentitem.url,
currentitem.price,
currentitem.quantity,
currentitem.color,
currentitem.color_name,
currentitem.product_id
)
binding.apply {
basketpic.visibility = View.GONE
emptyBasketText.visibility = View.GONE
}
viewModel.CalculateTotalPrice(items)
.observe(viewLifecycleOwner) {
binding.price.text = doubleToStringNoDecimal(it)
}
}
})
any help will be appreciated
UPDATE
you can see that first one is white and the second is brown but when i delete and undodelete the brown then white and reopen the fragment their position changes-> see second picture
after delete and undo deleteitems(first brown then white) and reopening the fragment
The Problem was that when i Clicked on Delete button the corresponding row in the Mysql was deleted and when i Clicked on Undo button the items that i deleted them
Were added to the table with new id and beacause of this the position of item changed after the fragment reopened
so i set a Snackbar.Callback to snackbar and i said that when it closed on its own then you can delete the item from database(mysql)
this is the code:
val snackbar = Snackbar.make(
viewHolder.itemView,
"این کالا از سبد خرید حذف شد",
Snackbar.LENGTH_LONG
)
snackbar.setAction("بازگرداندن") {
list.add(removedPosition, removedItem)
listener.SnackActionClicked(position)
notifyItemInserted(removedPosition)
}
snackbar.setActionTextColor(Color.YELLOW);
snackbar.addCallback(object : Snackbar.Callback() {
override fun onDismissed(snackbar: Snackbar?, event: Int) {
super.onDismissed(snackbar, event)
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT) {
// Snackbar closed on its own
// Delete item from mysql database
}
}
override fun onShown(snackbar: Snackbar?) {
super.onShown(snackbar)
}
});
snackbar.show();

Implement SearchView inside recyclerview header

I wanted to add a SearchView to my recyclerview. I wanted it to be at the top and scrollable with the items. To achieve this, I created separate adapter for my header and it contains the Searchview as well. Then I used a ConcatAdapter to combine this header adapter with the contents below it.
Initially I want all the items to be visible under the SearchView from _onBoardingState which is a MutableStateFlow and when user searches for a tag then the results for it get added to _onSearch which is also a MutableStateFlow.
I have this MutableStateFlow, _onBoardingState inside my ViewModel that gets the value from Firestore in the init of ViewModel. The number of results is less (~ 20) so there is no pagination implemented and all items get loaded at once.
Now, whenever user wants to search an item by a tag, the SearchView returns a Flow of the typed value and also a Flow that updates about if the SearchView is still open or closed. I used these extension functions for this:
fun SearchView.getQueryTextChangeStateFlow(onSubmit: ()-> Unit): StateFlow<String> {
val query = MutableStateFlow("")
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
onSubmit()
return true
}
override fun onQueryTextChange(newText: String): Boolean {
query.value = newText
return true
}
})
return query
}
fun SearchView.getActiveStateFlow(): StateFlow<Boolean> {
val isOpen = MutableStateFlow(false)
setOnSearchClickListener {
isOpen.value = true
}
setOnCloseListener {
isOpen.value = false
false
}
return isOpen
}
Inside my ViewModel I have
...
private val _onBoardingState: MutableStateFlow<Model?> = MutableStateFlow(null)
private val _onSearch: MutableStateFlow<Model?> = MutableStateFlow(null)
private val _isActive: MutableStateFlow<Boolean> = MutableStateFlow(false)
fun toggleSearchViewState(isActive: Boolean) {
_isActive.value = isActive
}
val cuurentFlow: Flow<Model?> =
_isActive.flatMapLatest { isActive ->
if (isActive) {
_onSearch
} else {
_onBoardingState
}
}
...
Now the issue here is, whenever the recyclerview is scrolled down, the SearchView gets recycled and hence the setOnCloseListener gets called for it. This causes the _isActive value to be set to false by the Header's Adapter so the value of cuurentFlow gets toggled which should not be happening.
I thought of a solution as to set the setOnCloseListener of SearchView inside the header adapter's onViewRecycled() to null, but this didn't help. Below is code for my Header Adapter as well if needed.
class OnBoardingHeaderAdapter(
private val context: Context,
) : RecyclerView.Adapter<OnBoardingHeaderAdapter.HeaderViewHolder>() {
private var queryTextListener: ((StateFlow<String>) -> Unit)? = null
private var searchViewListener: ((StateFlow<Boolean>) -> Unit)? = null
inner class HeaderViewHolder(binding: OnboardingHeaderItemBinding) :
RecyclerView.ViewHolder(binding.root) {
private val root = binding.headerRoot
val search = binding.search
fun bind(headerMetaData: HeaderMetaData) {
root.visibility =
if (headerMetaData.shouldShow)
View.VISIBLE
else
View.GONE
val searchEditText: EditText =
search.findViewById(androidx.appcompat.R.id.search_src_text)
searchEditText.setHintTextColor(context.resources.getColor(R.color.white))
searchEditText.setTextColor(context.resources.getColor(R.color.white))
}
}
fun setQueryTextListener(listener: (StateFlow<String>) -> Unit) {
this.queryTextListener = listener
}
fun setSearchViewListener(listener: (StateFlow<Boolean>) -> Unit) {
this.searchViewListener = listener
}
private val RECYCLER_COMPARATOR = object : DiffUtil.ItemCallback<HeaderMetaData>() {
override fun areItemsTheSame(oldItem: HeaderMetaData, newItem: HeaderMetaData) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: HeaderMetaData, newItem: HeaderMetaData) =
oldItem == newItem
}
val headerDiffer = AsyncListDiffer(this, RECYCLER_COMPARATOR)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
val binding = OnboardingHeaderItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return HeaderViewHolder(binding)
}
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
//holder.setIsRecyclable(false)
if (position < 1) {
val header = headerDiffer.currentList[position]
holder.bind(header)
}
queryTextListener?.let {
it(holder.search.getQueryTextChangeStateFlow() {
val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
val view: View = holder.search
imm.hideSoftInputFromWindow(view.windowToken,0)
})
}
searchViewListener?.let {
it(holder.search.getActiveStateFlow())
}
}
override fun getItemCount(): Int = headerDiffer.currentList.size
override fun onViewRecycled(holder: HeaderViewHolder) {
super.onViewRecycled(holder)
holder.search.setOnCloseListener(null)
}
}
I wanted to know what is the best approach to solve this issue, I think even if i use a recyclerview with multiple view types here for the header then still the recycling issue will be there.

Recyclerview selection: createSelectSingleAnything selects multiple when scrolling

I'm trying to create a horizontal recyclerview with a single selection. The code below works but if you scroll, then 2 or more objects can be selected, even though I specify the single selection. I guess it's because the onBindViewHolder is not called if the item is off the screen but when I call the notifyDataSetChanged the behavior is still the same. The callbacks are correct, but the selector background, shows behind multiple objects.
class CategoryAdapter(private val listener: CategoryListener) :
ListAdapter<CategoryListing, CategoryAdapter.ViewHolder>(CategoryListing.DIFF_COMPARATOR) {
init {
setHasStableIds(true)
}
var tracker: SelectionTracker<Long>? = null
...
override fun getItemId(position: Int): Long = position.toLong()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
tracker?.let {
if (!it.hasSelection() && position == 0) {
it.select(0)
}
}
holder.bind(
currentList[position],
listener,
tracker?.isSelected(position.toLong()) ?: false
)
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(
category: CategoryListing,
listener: CategoryListener,
isSelected: Boolean
) {
if (isSelected) selector.show() else selector.hide()
itemView.setOnClickListener {
tracker?.clearSelection() // this doesn't seem to do anything
// notifyDataSetChanged() // neither this
// tracker?.select(adapterPosition.toLong()) // or this
listener.onItemClicked(category, adapterPosition)
}
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition() = adapterPosition
override fun getSelectionKey() = itemId
}
}
Fragment:
rv_categories.adapter = categoryAdapter
rv_categories.setHasFixedSize(true)
selectionTracker = SelectionTracker.Builder<Long>(
CardsFragment::javaClass.name,
rv_categories,
CategoryItemKeyProvider(rv_categories),
CategoryDetailsLookup(rv_categories),
StorageStrategy.createLongStorage()
).withSelectionPredicate(SelectionPredicates.createSelectSingleAnything()).build()
categoryAdapter.tracker = selectionTracker
selectionObserver = CategorySelectionObserver(selectionTracker) { selectedPosition ->
onSelectionChanged(selectedPosition)
}
selectionTracker.addObserver(selectionObserver)
...
override fun onItemClicked(category: CardCategoryListing, position: Int) {
// load the data for that category
}
private fun onSelectionChanged(selectedItemPosition: Long) {
rv_categories.findViewHolderForAdapterPosition(selectedItemPosition.toInt())?.itemView?.performClick()
}
Can anyone help?

Button Checklist all on Recyclerview does not work

I have recyclerview with checkbox and I want to checklist all the data using button. I have trying this tutorial, but when i click the button, the log is call the isSelectedAll function but can't make the checkbox checked. what wrong with my code?
this is my adapter code
var isSelectedAll = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListApproveDeatilViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_list_approve_row, parent, false)
return ListApproveDeatilViewHolder(itemView)
}
private lateinit var mSelectedItemsIds: SparseBooleanArray
fun selectAll() {
Log.e("onClickSelectAll", "yes")
isSelectedAll = true
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ListApproveDeatilViewHolder, position: Int) {
val approve = dataSet!![position]
holder.soal.text = approve.title
holder.kategori.text = approve.kategori
if (!isSelectedAll){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
and this is my activity code
override fun onCreate(savedInstanceState: Bundle?) {
private var adapter: ListApproveDetailAdapter? = null
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_approve)
ButterKnife.bind(this)
getData()
// this is my button onclick code
select.setOnClickListener(){
if (select.getText().toString().equals("Select all")){
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
adapter?.selectAll()
select.setText("Deselect all")
} else {
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
select.setText("Select all")
}
}
}
//this is for get my data for the recyclerview
fun getData() {
val created_by = intent.getStringExtra(ID_SA)
val tgl_supervisi = intent.getStringExtra(TGL_SURVEY)
val no_dlr = intent.getStringExtra(NO_DLR)
API.getListApproveDetail(created_by, tgl_supervisi, no_dlr).enqueue(object : Callback<ArrayList<ListApprove>> {
override fun onResponse(call: Call<ArrayList<ListApprove>>, response: Response<ArrayList<ListApprove>>) {
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
recyclerviewApprove?.adapter = ListApproveDetailAdapter(tempDatas)
adapter?.notifyDataSetChanged()
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
swipeRefreshLayout.isRefreshing = false
}
override fun onFailure(call: Call<ArrayList<ListApprove>>, t: Throwable) {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_SHORT).show()
swipeRefreshLayout.isRefreshing = false
}
})
}
thankyou for any help :)
I am posting the answer with implementation of demo project. I haven't modified your code but as per your requirement i have done this.
MainActivity class:
class MainActivity : AppCompatActivity() {
var selectAll: Boolean = false;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) as RecyclerView
val btnSelectAll = findViewById<Button>(R.id.btnSelectAll) as Button
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
//crating an arraylist to store users using the data class user
val users = ArrayList<User>()
//adding some dummy data to the list
users.add(User("Piyush", "Ranchi"))
users.add(User("Mehul", "Chennai"))
users.add(User("Karan", "TamilNadu"))
users.add(User("Bela", "Kolkata"))
//creating our adapter
val adapter = CustomAdapter(users, selectAll)
//now adding the adapter to recyclerview
recyclerView.adapter = adapter
btnSelectAll.setOnClickListener {
if (!selectAll) {
selectAll = true
} else {
selectAll = false
}
adapter?.selectAllCheckBoxes(selectAll)
}
}
}
User class:
data class User(val name: String, val address: String)
Adapter class:
class CustomAdapter(val userList: ArrayList<User>, val selectAll: Boolean) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
var selectAllA = selectAll;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_layout, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: CustomAdapter.ViewHolder, position: Int) {
holder.textViewName.text = userList[position].name;
if (!selectAllA){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
//this method is giving the size of the list
override fun getItemCount(): Int {
return userList.size
}
//the class is hodling the list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName = itemView.findViewById(R.id.textViewUsername) as TextView
val checkBox = itemView.findViewById(R.id.checkbox) as CheckBox
}
fun selectAllCheckBoxes(selectAll: Boolean) {
selectAllA = selectAll
notifyDataSetChanged()
}
}
As i already mentioned in comments you are using two different adapter instance .
Now i see you have declared adapter globally .
Just modify your code as follows and make sure response.body() have data int it :
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
adapter = ListApproveDetailAdapter(tempDatas)
recyclerviewApprove?.adapter=adapter
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
Add one variable in model class.
like var isSelect : Boolean
In your selectAll() method update adpter list and notify adapter.
Edit:
in the adapter class.
if (approve.isSelect){
holder.checkBox.setChecked(true)
} else {
holder.checkBox.setChecked(false)
}
Hope this may help you.
OR
If you are using AndroidX then use should use one recyclerview features.
androidx.recyclerview.selection
A RecyclerView addon library providing support for item selection. The
library provides support for both touch and mouse driven selection.
Developers retain control over the visual representation, and the
policies controlling selection behavior (like which items are eligible
for selection, and how many items can be selected.)
Reference from here

Categories

Resources