I'd appreciate your help please. I want to build a custom Checkbox Group like in the food delivery apps(uber eats, deliveroo). When the user select/unselect one item it should adjust the price automatically. This screen is built using a custom checkbox for each item, a custom checkbox group, two-way data binding, and epoxy. When select/unselect an item the correct binding adapter is called, but it never update the viewmodel.
Here is the custom checkbox.
class CheckboxCustom #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
private val root: ViewGroup
private val tvTitle: AppCompatTextView
private val tvPrice: AppCompatTextView
private val checkbox: AppCompatCheckBox
init {
inflate(context, R.layout.checkbox_custom, this)
root = findViewById(R.id.root)
tvTitle = findViewById(R.id.tvTitle)
tvPrice = findViewById(R.id.tvPrice)
checkbox = findViewById(R.id.checkbox)
checkbox.isChecked
root.setOnClickListener {
checkbox.toggle()
this.callOnClick()
}
}
fun setTitle(title: String) {
tvTitle.text = title
}
fun setPrice(price: String) {
tvPrice.text = price
}
fun isChecked(): Boolean = checkbox.isChecked
}
This is the custom checkbox group:
class CheckBoxGroup #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) {
val parent: LinearLayout
private lateinit var mOnCheckedChangeListener: OnCheckedChangeListener
var totalPrice: Double = 0.0
init {
inflate(context, R.layout.checkbox_group, this)
parent = findViewById(R.id.root)
}
fun setItems(toppings: Array<Topping>) {
toppings.forEach { topping ->
val checkBox = CheckboxCustom(context)
checkBox.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
checkBox.setTitle(topping.name)
checkBox.setPrice(topping.price.toString())
checkBox.setOnClickListener {
Timber.tag(TAG).d("checkbox checked: %s", checkBox.isChecked())
if(checkBox.isChecked()) {
totalPrice+= topping.price
mOnCheckedChangeListener.onCheckedChange(totalPrice)
} else {
totalPrice-= topping.price
mOnCheckedChangeListener.onCheckedChange(totalPrice)
}
}
parent.addView(checkBox)
}
}
fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
listener?.let { this.mOnCheckedChangeListener = it}
}
}
interface OnCheckedChangeListener {
fun onCheckedChange(totalPrice: Double)
This is the binding adapters
#BindingAdapter("setItems")
fun setItems(checkboxGroup: CheckBoxGroup, toppings: Array<Topping>) {
checkboxGroup.setItems(toppings)
}
#BindingAdapter(value = ["setOnCheckedChangeListener", "totalPriceAttrChanged"] , requireAll = false)
fun setOnCheckedChangeListener(checkboxGroup: CheckBoxGroup, listener: OnCheckedChangeListener,
totalPriceAttrChanged: InverseBindingListener) {
checkboxGroup.setOnCheckedChangeListener(listener)
if (totalPriceAttrChanged != null) {
totalPriceAttrChanged.onChange()
}
}
#BindingAdapter( "totalPrice")
fun setTotalPrice(checkboxGroup: CheckBoxGroup, totalPrice: Double) {
Timber.d("setTotalPrice binding called")
if(checkboxGroup.totalPrice != totalPrice) {
checkboxGroup.totalPrice.plus(1.0)
}
}
#InverseBindingAdapter(attribute = "totalPrice")
fun getTotalPrice(checkboxGroup: CheckBoxGroup): Double {
Timber.d("getTotalPrice binding called")
return checkboxGroup.totalPrice
}
The item checkbox where checkbox is used, it's an epoxy item.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="toppings"
type="com.jephtecolin.model.Topping[]" />
<variable
name="listener"
type="com.jephtecolin.kwii.ui.custom.OnCheckedChangeListener" />
<variable
name="viewModel"
type="com.jephtecolin.kwii.ui.consumable_detail.ConsumableDetailViewModel" />
</data>
<com.jephtecolin.kwii.ui.custom.CheckBoxGroup
android:id="#+id/gp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:totalPrice="#={viewModel.totalCost}"
setOnCheckedChangeListener="#{listener}"
setItems="#{toppings}"/>
</layout>
This is the viewmodel:
#HiltViewModel
class ConsumableDetailViewModel #Inject constructor() : ObservableViewModel() {
val topping = Topping("1", "Hot Cheese", 30.00)
val consumable = Consumable(
"1",
"Griot de boeuf",
"Food",
250.00,
"https://www.innovatorsmag.com/wp-content/uploads/2017/03/IF-Burger-1024x772.jpg",
"Griot de boeuf en sauce, servis avec des bananes pese, des frites et du salade",
"Boeuf, Banane, Choux, Radis",
)
val totalCost: ObservableDouble = ObservableDouble(0.0)
#Bindable get() {
return field
}
}
This is the custom viewmodel
open class ObservableViewModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry by lazy { PropertyChangeRegistry() }
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
callbacks.remove(callback)
}
/**
* Notifies listeners that all properties of this instance have changed.
*/
#Suppress("unused")
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with [Bindable] to generate a field in
* `BR` to be used as `fieldId`.
*
* #param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
This code is from the fragment, and it's not being triggered:
viewModel.totalCost.addOnPropertyChangedCallback(
object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
Toast.makeText(context, (sender as ObservableDouble).get().toString(), Toast.LENGTH_LONG).show()
Timber.d(" totalPrice :" + (sender as ObservableDouble).get())
}
})
This code also is from the fragment, and not being triggered either
rvConsumableDetail.withModels {
itemCheckbox {
id("topping")
toppings(toppings.toTypedArray())
listener(object : OnCheckedChangeListener {
override fun onCheckedChange(totalPrice: Double) {
// todo("Not yet implemented")
Timber.d("itemCheckBox totalPrice :" + totalPrice)
Toast.makeText(context, "itemCheckBox totalPrice :" + totalPrice, Toast.LENGTH_LONG).show()
}
})
}
}
The binding adapters seems to work fine, when I put breakpoints inside them it works, but for some reason, observing for change on totalPrice from the fragment never work.
Thanks for helping
You are having 2 statements for check change listener in your binding adapter method. This would be causing the listener objects to be replaced. So you are not getting the events.
checkboxGroup.setOnCheckedChangeListener(listener)
checkboxGroup.setOnCheckedChangeListener(object : OnCheckedChangeListener {
override fun onCheckedChange(totalPrice: Double) {
Timber.d("binding adapter works")
setTotalPrice(checkboxGroup, totalPrice)
if (totalPriceAttrChanged != null) {
totalPriceAttrChanged.onChange()
}
}
})
Related
I got some categories from an api and trying to show them on a recycler view but it doesn't work for some reason.
Although the data appears correctly in the logcat, it is sent as null to the Category adapter.
This is the Main Activity (where I'm trying to show the data):
`
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MEALZ"
private lateinit var binding: ActivityMainBinding
private val viewModel:MealsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = CategoryAdapter(this)
binding.categoriesRv.adapter = adapter
viewModel.getMeals()
lifecycleScope.launch {
viewModel.categories.collect {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
}
}
`
This is Recycler Category Adapter :
`
class CategoryAdapter(private val context: Context?) :
RecyclerView.Adapter<CategoryAdapter.CategoryViewHolder>() {
private var categoryList: MutableList<Category?> = mutableListOf<Category?>()
inner class CategoryViewHolder(itemView: CategoryLayoutBinding) :
RecyclerView.ViewHolder(itemView.root) {
val name = itemView.categoryNameTv
val img = itemView.categoryIv
val des = itemView.categoryDesTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = CategoryLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
var category = categoryList[position]
holder.name.text = category?.strCategory
holder.des.text = category?.strCategoryDescription
Glide.with(context as Context).load(category?.strCategoryThumb).into(holder.img)
}
override fun getItemCount(): Int {
return categoryList.size
}
fun setData(CategoryList: List<Category>) {
this.categoryList.addAll(CategoryList)
notifyDataSetChanged() //to notify adapter that new data change has been happened to adapt it
}
}
`
This is the View Model class:
#HiltViewModel
class MealsViewModel #Inject constructor(private val getMealsUseCase: GetMeals): ViewModel() {
private val TAG = "MealsViewModel"
private val _categories: MutableStateFlow<CategoryResponse?> = MutableStateFlow(null)
val categories: StateFlow<CategoryResponse?> = _categories
fun getMeals() = viewModelScope.launch {
try {
_categories.value = getMealsUseCase()
} catch (e: Exception) {
Log.d(TAG, "getMeals: ${e.message.toString()}")
}
}
}
you create your _categories with null as initial value, so first value of categories flow will be null and only second one will contain fetched data. As a workaround, you can check that data is not null:
viewModel.categories.collect {
if (it != null) {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
or introduce some kind of "loading" state
I asked this question before but I could not get any answer. So I am asking it again but this time more detailed so you guys can understand my problem. I have a RecyclerView that gets items from my Firebase database. I want to select multiple items and add highlighted items to an arraylist of strings. FYI I am using a library called Groupie. I am not using custom adapter.
Hobbies Class
class Hobbies : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hobbies)
val database = FirebaseDatabase.getInstance()
tophobbies.layoutManager =
object : LinearLayoutManager(this, HORIZONTAL, false) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
// force height of viewHolder here, this will override layout_height from xml
lp.height = recyclerlinear.height
return true
}
}
val adapter = GroupAdapter<GroupieViewHolder>()
val reference = database.getReference("Hobbies")
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snap in snapshot.children) {
val hobbiesItem = snap.getValue(HobbiesClass::class.java)
if (hobbiesItem != null) {
adapter.add(HobbiesAdapter(hobbiesItem))
}
}
tophobbies.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
HobbiesAdapter
class HobbiesAdapter(val hobbyItem: HobbiesClass) : Item<GroupieViewHolder>() {
var list: ArrayList<String> = ArrayList()
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.hobbynameTV.text = hobbyItem.hobbyName
Picasso.get().load(hobbyItem.imageUrl).into(viewHolder.itemView.hobbyImageView)
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
}
}
}
override fun getLayout(): Int {
return R.layout.row
}
}
HobbiesClass
#Parcelize
class HobbiesClass(val hobbyName: String, val imageUrl: String,var isSelected:Boolean) : Parcelable {
constructor() : this("", "",false)
}
My item row xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="164dp"
android:layout_height="70dp"
android:layout_marginStart="5dp"
android:background="#android:color/transparent"
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="#+id/frameHobby"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/hobbiesbackground">
<TextView
android:id="#+id/hobbynameTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Camping"
android:textColor="#000000"
android:fontFamily="#font/extralight"
android:layout_gravity="center|right"
android:layout_marginEnd="15dp"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/hobbyImageView"
android:layout_width="54dp"
android:layout_height="47dp"
android:layout_gravity="center|left"
android:layout_marginStart="12dp"/>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I can change items background with
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
}
}
What I want to do is: Add the selected elements names to my arraylist. If I use the code provided under it adds the name string to the array but when I click to another one the previous one gets deleted.(It looks like the array is not saved, it resets everytime I press an item) Also if I click on multiple items rapidly then it adds them. But if I click on an item split second later all previous items from the arraylist get deleted. How can I fix this? I have looked on multiple videos but still could not get any answers.
viewHolder.itemView.setOnClickListener {
list.add(viewHolder.itemView.hobbynameTV.text.toString())
}
Thank you in advance!
I have found the solution. I knew the problem that was making my arraylist reset everytime was that the bind method runs everytime it is clicked. So I made a mutable list called selectedList in my Hobbies class where the recyclerview is stored. Then I past it as a parameter in my HobbiesAdapter. Then in My Hobbies class I changed the adapter from adapter.add(HobbiesAdapter(hobbiesItem))
to adapter.add(HobbiesAdapter(hobbiesItem,selectedItems))
Here is how my code looks like now. I hope it will help others in the future
Hobbies class
class Hobbies : AppCompatActivity(){
private val selectedItems = mutableListOf<String>()
#SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hobbies)
val database = FirebaseDatabase.getInstance()
tophobbies.layoutManager =
object : LinearLayoutManager(this, HORIZONTAL, false) {
override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
// force height of viewHolder here, this will override layout_height from xml
lp.height = recyclerlinear.height
return true
}
}
val adapter = GroupAdapter<GroupieViewHolder>()
val reference = database.getReference("Hobbies")
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snap in snapshot.children) {
val hobbiesItem = snap.getValue(HobbiesClass::class.java)
if (hobbiesItem != null) {
adapter.add(HobbiesAdapter(hobbiesItem,selectedItems))
}
}
tophobbies.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
letsgobtn.setOnClickListener {
Toast.makeText(this,"Selected item is ${selectedItems.toString()}",Toast.LENGTH_SHORT).show()
}
}
}
HobbiesAdapter class
class HobbiesAdapter(val hobbyItem: HobbiesClass,val selectedItems: MutableList<String>) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.hobbynameTV.text = hobbyItem.hobbyName
Picasso.get().load(hobbyItem.imageUrl).into(viewHolder.itemView.hobbyImageView)
viewHolder.itemView.setOnClickListener {
if (viewHolder.itemView.isSelected){
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackground)
viewHolder.itemView.isSelected = false
selectedItems.remove(viewHolder.itemView.hobbynameTV.text.toString())
}else {
viewHolder.itemView.frameHobby.setBackgroundResource(R.drawable.hobbiesbackgroundselected)
viewHolder.itemView.isSelected = true
selectedItems.add(viewHolder.itemView.hobbynameTV.text.toString())
}
}
}
override fun getLayout(): Int {
return R.layout.row
}
}
HobbiesClass class
#Parcelize
class HobbiesClass(val hobbyName: String, val imageUrl: String,var isSelected:Boolean) : Parcelable {
constructor() : this("", "",false)
}
I made selectDelete button on the main activity instead of RecyclerView.
When I click that button, I want to delete the items which are checked.
There is an error that checks the RecyclerView item checkbox and unchecks the item when scrolling.
Activity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val tag = this::class.java.simpleName
lateinit var adapter: CartItemRecyclerAdapter
var itemList: MutableList<CartItemDataVo> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
selectDelete.setOnClickListener {
if (checkBox.isChecked) {
*adapter.removeItems()*
}
}
swipeRefreshLo.setOnRefreshListener(this)
itemList.add(CartItemDataVo("item1", 1, 16800, "cart_doll", false))
itemList.add(CartItemDataVo("item2", 1, 16800, "cart_cup", false))
itemList.add(CartItemDataVo("item3", 1, 30000, "cart_perfume", false))
itemList.add(CartItemDataVo("item4", 1, 16800, "cart_fan", false))
adapter = CartItemRecyclerAdapter(this, this, itemList)
recycler_view.adapter = adapter
recycler_view.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(applicationContext)
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}
Adapter:
class CartItemDataVo(
title: String,
itemNumber: Int,
pointValue: Int,
imageView: String,
CheckBox: Boolean
) {
var title: String = title
var itemNumber: Int = itemNumber
var pointValue: Int = pointValue
var image: String = imageView
var isChecked: Boolean = CheckBox
}
class CartItemRecyclerAdapter(
val context: Context,
private var activity: Activity,
private var dataList: MutableList<CartItemDataVo>
) : RecyclerView.Adapter<CartItemRecyclerAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(context).inflate(R.layout.cart_item_list, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder?.bind(dataList[position], context)
}
override fun getItemCount(): Int = dataList.size
*#SuppressLint("NewApi")
fun removeItems() {
dataList.removeIf { it.isChecked }
notifyDataSetChanged()
}*
fun toggleItems() {
for (item: CartItemDataVo in dataList) {
var state = item.isChecked
item.isChecked = state.not()
}
notifyDataSetChanged()
}
inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
var titleText = itemView?.findViewById(R.id.titleText) as TextView
var temNumerTextt = itemView?.findViewById(R.id.textViewItemNumer) as TextView
var pointValueText = itemView?.findViewById(R.id.pointValueText) as TextView
var imageView = itemView?.findViewById(R.id.imageView) as ImageView
var checkBox = itemView?.findViewById(R.id.checkBox) as CheckBox
fun bind(data: CartItemDataVo, context: Context) {
if (data.image != "") {
val resourceId =
context.resources.getIdentifier(data.image, "drawable", context.packageName)
imageView?.setImageResource(resourceId)
} else {
imageView.setImageResource(R.mipmap.ic_launcher)
}
titleText?.text = data.title
temNumerTextt?.text = data.itemNumber.toString()
pointValueText?.text = data.pointValue.toString() + "P"
if (data.isChecked) {
checkBox.buttonDrawable =
checkBox.context.getDrawable(R.drawable.check_box_active_cs)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.VISIBLE
} else {
checkBox.buttonDrawable = checkBox.context.getDrawable(R.drawable.check_box_no)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.GONE
}
checkBox?.setOnClickListener {
if (checkBox.isChecked == data.isChecked) {
checkBox.buttonDrawable = it.context.getDrawable(R.drawable.check_box_active_cs)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.VISIBLE
} else {
checkBox.buttonDrawable = it.context.getDrawable(R.drawable.check_box_no)
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
layout.visibility = View.GONE
}
}
}
}
}
First of all, replace
var itemList: MutableList<CartItemDataVo> = arrayListOf()
with
val itemList: MutableList<CartItemDataVo> = arrayListOf()
You don't want mutable property, which is also mutable collection at the same time. This is very bad practice.
Same situation in adapter
private var dataList : MutableList<CartItemDataVo>
replace with
private val dataList : MutableList<CartItemDataVo>
Then remove private var activity : Activity from your adapter's constructor. Don't put any references to Activity or Fragment in adapter!
Adapter should be only responsible for displaying list.
If in any case, you need communication between Activity or Fragment and adapter, use interface instead. This is not ViewHolder responsibility to hold reference to parent layout and manipulate it!
val layout = activity?.findViewById(R.id.layoutOrder) as LinearLayout
All lines like below should be removed from ViewHolder. If you need set something which belongs to Activity, based on action on list, use interface.
Finally, in adapter add method which will remove checked items from it:
fun removeItems() {
itemList.removeIf { it.isChecked }
notifyDataSetChanged()
}
Add the following line right after checkBox?.setOnClickListener { (as first line in listener)
data.isChecked = !data.isChecked
And replace
selectDelete.setOnClickListener {
if(checkBox.isChecked){
}
}
with
selectDelete.setOnClickListener {
if(checkBox.isChecked){
adapter?.removeItems()
}
}
Bonus:
read about data class, and use it for CartItemDataVo (don't use CheckBox in constructor),
updateData method can be optimized using DiffUtil for RecyclerView,
it can be improved by modifying data in Activity, not in adapter, so responsible would be moved to better place,
read about SOLID principles,
read about MVVM and MVP design patterns.
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
I want to have a class derived from LinearSmoothScroller class, in which I could pass a scrolling speed property by a class constructor. I've got a code below, which works and have added some println()'s for tests.
ScrollerManager class:
class ScrollerManager(
val context: Context,
private var millisecondsPerInch: Float
) : LinearSmoothScroller(context) {
init {
println("init millisecsPerInch: $millisecondsPerInch, instance: $this")
}
fun printSpeed() {
println("printSpeed(): $millisecondsPerInch, instance: $this")
}
override fun getHorizontalSnapPreference(): Int {
return LinearSmoothScroller.SNAP_TO_START
}
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
println("calcSpeedPerPixel(..) millisecsPerInch: ${this.millisecondsPerInch}, instance: $this")
// Here I want to use millisecondsPerInch field value, instead of written value 30f:
return 30f / displayMetrics?.densityDpi!!
}
}
Click item listener interface:
interface RecItemClickListener {
fun onClick(view: View, position: Int)
}
An adapter. Context, RecyclerView, and itemList are passed from Actvity.
When recyclerView item is clicked, a scrollToPosition method is called where my SmoothScroller is used.
class AdvancedRecViewHoriAdapter(
val context: Context,
val recyclerView: RecyclerView,
val itemsList: List<String>
) : RecyclerView.Adapter<AdvancedRecViewHoriCustomViewHolder>() {
companion object {
var selectedPosition = RecyclerView.NO_POSITION
}
override fun getItemCount(): Int {
return itemsList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: AdvancedRecViewHoriCustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
// inflate layout from xml
val cellForRow = layoutInflater.inflate(
R.layout.rec_view_advanced_hori_row, parent, false)
return AdvancedRecViewHoriCustomViewHolder(cellForRow)
}
private fun scrollToPosition(position: Int) {
val smoothScroller = ScrollerManager(context, 30f)
smoothScroller.printSpeed()
smoothScroller.targetPosition = position
recyclerView.layoutManager.startSmoothScroll(smoothScroller)
}
override fun onBindViewHolder(holder: AdvancedRecViewHoriCustomViewHolder, position: Int) {
val itemName = itemsList[position]
itemView.rec_view_adv_hori_row_text_view_id.text = itemName
holder.itemClickListener = object : RecItemClickListener {
override fun onClick(view: View, position: Int) {
selectedPosition = position // Update layout position of clicked item.
scrollToPosition(position)
}
}
}
}
A custom ViewHolder:
class AdvancedRecViewHoriCustomViewHolder(val view: View) :
RecyclerView.ViewHolder(view), View.OnClickListener {
var itemClickListener: RecItemClickListener? = null
init {
view.setOnClickListener(this#AdvancedRecViewHoriCustomViewHolder)
}
override fun onClick(view: View) {
try {
itemClickListener?.onClick(view, adapterPosition)
} catch (exception: NullPointerException) {
Log.e("NullPointerException", "Item view is null")
}
}
}
The problem is that when I click one of the recyclerView items I get system prints like this. As Pawel has given an advice, I've checked and I'm adding addresses of instances. But, prints come from the same instances, so it's not solving this issue.
I/System.out:
calcSpeedPerPixel(..) inchPerMilisecs: 0.0, instance: com.example.danielg.loginviewexercise.ScrollerManager#1c4f5c5
init inchPerMilisecs: 30.0, instance: com.example.danielg.loginviewexercise.ScrollerManager#1c4f5c5
printSpeed(): 30.0, instance: com.example.danielg.loginviewexercise.ScrollerManager#1c4f5c5
The question is:
Why millisecsPerInch doesn’t have the value passed in a ScrollerManager class constructor and its value is 0.0?
I've done some research and have looked inside LinearSmoothScroller class implementation. Maybe the problem is that a calculateSpeedPerPixel method is protected. I've also considered overriding a value of MILLISECONDS_PER_INCH property, but it's final.
Listing from LinearSmoothScroller.java file:
private static final float MILLISECONDS_PER_INCH = 25f;
...
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}