1

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>
Hamed Goharshad
  • 631
  • 4
  • 12
Vlad Duta
  • 33
  • 8
  • Can you please elaborate the question with what exactly you want to achieve? – Shobhith Jul 24 '22 at 14:46
  • I wanna make a "checkListener" so that every time I check the checkbox coresponding 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 – Vlad Duta Jul 24 '22 at 15:05

3 Answers3

1

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

Hamed Goharshad
  • 631
  • 4
  • 12
1

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

}
  • so how can I pass this variable "calorieSum" to a TextView to see if it stores the data correctly ? – Vlad Duta Jul 24 '22 at 17:12
  • You may convert calorieSum into MutableLiveData and observe it OR Log.d("DEBUG", valorieSum.toString()) to print it into the console \n https://developer.android.com/topic/libraries/architecture/livedata – gelbelachente Jul 24 '22 at 17:17
  • I create :class SharedViewModel : ViewModel() {private var _calorieSum= MutableLiveData(0.0) val calorieSum : LiveData = _calorieSum – Vlad Duta Jul 24 '22 at 18:06
  • and call it in fragment_information XML : android:text="@{@string/ramase+viewModel.calorieSum.toString()}" ............... but when I check items the value of calorieSum remain 0 – Vlad Duta Jul 24 '22 at 18:08
  • val cs = LiveData get()= _cs, because right now you copy the initial _cs, when it's still 0.0 and you don't 'point' to its value – gelbelachente Jul 25 '22 at 04:39
1

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!


If you're having trouble with the listener function, you could just add a function to your InformationFragment, pass that fragment to your adapter, and call the function directly:

// inside InformationFragment
fun onCalorieTotalChanged(total: Int) {
    // display it
}

// Pass the InformationFragment to your adapter and call the function
class AdapterItem(
    val userList: List<MyDataItem>,
    private val informationFragment: InformationFragment
) : RecyclerView.Adapter<AdapterItem.ViewHolder>()  {
 
    private fun setCheckedState(position: Int, checked: Boolean) {
        ...
        // push the new total to the listener function
        informationFragment.onCalorieTotalChanged(total)
    }
}


// creating the adapter, passing the fragment in
adapterItem = AdapterItem(responseBody, this@InformationFragment)

An interface would be better but hey

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • setCheckedState(bindingAdapterPosition, checkBox.isChecked) here it says Unresolved reference: bindingAdapterPosition – Vlad Duta Jul 24 '22 at 17:42
  • @VladDuta it's part of the `RecyclerView` library since 1.2.0: https://developer.android.com/jetpack/androidx/releases/recyclerview#recyclerview-1.2.0 If you don't want to update, you can just use the old `adapterPosition` property – cactustictacs Jul 24 '22 at 17:52
  • where exactly in my fragment should I pass this (onCreateView, onViewCreated, )?.... :myRecyclerView.adapter = AdapterItem(users) { calorieTotal -> someTextView.text = "Total calories: $calorieTotal" } – Vlad Duta Jul 24 '22 at 18:53
  • and myRecyclerView should I change it with the id from XML (recyclerview_lista) ? – Vlad Duta Jul 24 '22 at 18:54
  • and also in this function I get some errors "Function declaration must have a name" – Vlad Duta Jul 24 '22 at 18:56
  • Now in my fragment in the function getMyData() , when I call AdapterItem( adapterItem = AdapterItem(responseBody) ) I need to pass a value for calorieListener parameter... what sould I pass there? – Vlad Duta Jul 24 '22 at 19:14
  • @VladDuta Whenever you create the `AdapterItem`, you pass in a lambda that does something with the updated calorie total - the example I did there is setting the text on a `TextView` that you have access to when you're creating the adapter. Imagine you had a variable called `calorieTotal` in your adapter - how would you display that? Whatever code you run to do that, put it in the lambda that you pass to the `AdapterItem` constructor. Or just change it so you pass the `Fragment` or something - I'll add an example to my answer – cactustictacs Jul 24 '22 at 19:33