Home > Software engineering >  Showing sum of selected RecyclerView's items amount
Showing sum of selected RecyclerView's items amount

Time:07-25

What should I add in my code so that every time I check one of the recycler view's items it will display his number of calories? I wanna make a "checkListener" so that every time I check the checkbox corresponding to the selected item of the recyclerview ... his calories number will be stored in a variable and if I check for example 2 of these items the variable will store their sum

AdapterItem:

class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>()  {

    class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
        var numeLista: TextView
        var caloriiLista: TextView = itemView.findViewById(R.id.caloriiLista)
        init{
            numeLista=itemView.numeLista
            caloriiLista=itemView.caloriiLista
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        var itemView = LayoutInflater.from(parent.context).inflate(R.layout.row_items, parent, false)
        return ViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.numeLista.text=userList[position].foodItems.get(0).foodName
        holder.caloriiLista.text=userList[position].foodItems.get(0).calories.toString()
    }

    override fun getItemCount(): Int {
        return userList.size
    }
}


**The fragment where the Recycle is displayed:**

const val BASE_URL= "https://raw.githubusercontent.com/terrenjpeterson/caloriecounter/master/src/data/"


class InformationFragment : Fragment() {

    private var binding: FragmentInformationBinding? = null
    private val sharedViewModel: SharedViewModel by activityViewModels()

    lateinit var adapterItem: AdapterItem
    lateinit var linearLayoutManager: LinearLayoutManager


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        getMyData()
        val fragmentBinding = FragmentInformationBinding.inflate(inflater, container, false)
        binding = fragmentBinding
        return fragmentBinding.root
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        linearLayoutManager = LinearLayoutManager(this.context)
        recyclerview_lista.setHasFixedSize(true)
        recyclerview_lista.layoutManager = linearLayoutManager

        binding?.apply {
            viewModel = sharedViewModel
            informationFragment = this@InformationFragment
            lifecycleOwner = viewLifecycleOwner
        }
    }

    private fun getMyData() {
        val retrofitBuilder = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .build()
            .create(ApiInterface::class.java)

        val retrofitData = retrofitBuilder.getData()

        retrofitData.enqueue(object : Callback<List<MyDataItem>?> {
            override fun onResponse(
                call: Call<List<MyDataItem>?>,
                response: Response<List<MyDataItem>?>
            ) {
                val responseBody = response.body()!!
                adapterItem = AdapterItem(responseBody)
                adapterItem.notifyDataSetChanged()
                recyclerview_lista.adapter=adapterItem
            }


            override fun onFailure(call: Call<List<MyDataItem>?>, t: Throwable) {
                d("informationFragment", "onFailure: "   t.message)
            }
        })
    }
}


**Fragment XML** 


 <androidx.constraintlayout.widget.ConstraintLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".InformationFragment"
       >




        <androidx.recyclerview.widget.RecyclerView
            android:id="@ id/recyclerview_lista"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="40dp"/>

        <TextView
            android:id="@ id/activitate_txt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="4dp"
            android:padding="8dp"
            android:text="@{@string/ramase viewModel.ideal.toString()}"
            android:textSize="18dp"
            app:layout_constraintTop_toTopOf="@id/recyclerview_lista"
            tools:ignore="MissingConstraints"
            android:layout_marginTop="-40dp"/>
    </androidx.constraintlayout.widget.ConstraintLayout>


**row_items XML:**


<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@ id/numeLista"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10sp"
            android:text="Nume"
            android:textSize="20sp">

        </TextView>

        <TextView
            android:id="@ id/caloriiLista"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10sp"
            android:text="calorii"
            android:textSize="20sp">

        </TextView>

        <CheckBox
            android:id="@ id/checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           />

    </LinearLayout>



</androidx.cardview.widget.CardView>

CodePudding user response:

Android: checkbox listener First you need to add the checkBox to your ViewHolder

Then you apply a checkbox listener specified in the link above. when isChecked is true, then you add the sum of its calorie to an integer, if not you remove it:

var calorieSum : Int = 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val food = userList[position].foodItems.first()!!
    holder.numeLista.text= food.foodName
    holder.caloriiLista.text=food.calories.toString()

    holder.checkBox.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {

       override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) {
  if (isChecked){
            calorieSum  = food.calories
        }else{
            calorieSum -= food.calories
       }
     }
 });    

}

CodePudding user response:

The simplest way but not the most efficient one may be this:

consider a sum variable in the adapter:

class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>()  {

var sum = 0

and set a listener to your items:


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.numeLista.text=userList[position].foodItems.get(0).foodName
    holder.checkBox.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {         
override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) {
  if (isChecked){
            calorieSum  = food.calories
        }else{
            calorieSum -= food.calories
       }
     }
 });    
...
    }

This is just a Pseudocode to explain my idea and may need to change a bit

CodePudding user response:

If you're using check boxes in a RecyclerView, you need to keep track of which ones are checked anyway - the ViewHolders get reused as you scroll, and you need to be able to check or uncheck the box in each ViewHolder depending on whether the item it's displaying is supposed to be checked. If you don't, the checkbox will just "stick" as you scroll, showing a check for all the other items that happen to display in that VH, because you're never updating it.

So you need to do something like this:

class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>()  {

    // a collection of all the item positions that are checked
    private val checkedItems = mutableSetOf<Int>()

    // a function to handle storing the checked state for a particular item
    private fun setCheckedState(position: Int, checked: Boolean) {
        if (checked) checkedItems.add(position)
        else checkedItems.remove(position)
    }

    // needs to be an -inner- class so it can access the setCheckedState function
    // on its parent class
    inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
        // store a reference to this VH's checkbox
        val checkBox = itemView.findViewById<CheckBox>(R.id.checkBox)

        init {
            // a click listener is easier than a change listener, since the latter
            // will fire if you update the checked state yourself, but a click
            // listener won't
            checkBox.setOnClickListener {
                // store the checked state for the item this VH is displaying
                setCheckedState(bindingAdapterPosition, checkBox.isChecked)
            }
        }
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        // Set the checkbox state depending on whether this item is selected
        holder.checkBox.isChecked = position in checkedItems
    }
}

Now you can scroll and onBindViewHolder can set each item's checked state properly. (You can also save and restore checkedItems to maintain the checked state during activity recreation etc.)


So now you're keeping track of your checked items, it's easy enough to update a total - just recalculate it and display it whenever setCheckedState is called!

// your internal total if you need one
var total: Int = 0

private fun setCheckedState(position: Int, checked: Boolean) {
    if (checked) checkedItems.add(position)
    else checkedItems.remove(position)
    // recalculate the total - here's one way to do it
    total = checkedItems.sumOf { index -> userItems[index].calories }
    // or a safer way, if you can't guarantee the checked items are 
    // still in the user list for some reason:
    total = checkedItems.sumBy { index -> userItems.getOrNull(index])?.calories ?: 0 }
}

Or, arguably a better way to do it:

val total: Int get() = checkedItems.sumOf { index -> userItems[index].calories }

This way total always reflects the current state of the checked items - you don't need to update the checked items and then also update the total, this way it's sort of automatic. The tradeoff is that it gets recalculated every time the value is read, but you shouldn't need to read it often anyway in this situation, probably the same number of times you'd update it manually (since you're pushing updates when they occur, you're not actively polling it watching for changes).


Now you also need to push the update to whatever needs to display that total - lots of ways to do this too, doing it through a ViewModel would be best if you're already using those, but we can do a listener function too:

class AdapterItem(
    val userList: List<MyDataItem>,
    private val calorieListener: (Int) -> Unit)
) : RecyclerView.Adapter<AdapterItem.ViewHolder>()  {

Here we're passing in a function that we can call with the current calorie total whenever it updates. So in your Activity or whatever, you can do this:

myRecyclerView.adapter = AdapterItem(users) { calorieTotal ->
    someTextView.text = "Total calories: $calorieTotal"
}

And now the adapter has a function that will update a TextView with the new total. So you just need to call it whenever checkedItems updates:

private fun setCheckedState(position: Int, checked: Boolean) {
    ...
    // push the new total to the listener function
    calorieListener(total)
}

And that's basically it!

  • Related