0

In my case I am trying to get a list of String from my Adapter and use it in my Fragment but upon debugging using Logs I found that the list is getting updated inside the onBindViewHolder but not outside it. So when I try to access the list from my Fragment I am getting an empty list of String.

I have spent few hours trying to figure this but can't find a feasible solution.

My Approach: I am thinking of an approach to save this list in a room table and then query it back in the Fragment. Though it may solve the issue but is it the only way? Are there any other ways to achieve this result?

My Adapter

class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() {

    var floors = emptyList<String>()

    inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) :
        RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false)

        return MyViewHolder(binding)
    }

    private val checkedFloors: MutableList<String> = mutableListOf()

    //List of uniquely selected checkbox to be observed from New Schedule Floor Fragment
    var unique: List<String> = mutableListOf()

    @SuppressLint("SetTextI18n")
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentFloor = floors[position]
        Timber.d("Current floor: $currentFloor")
        holder.binding.floorCheckBox.text = "Floor $currentFloor"

        //Checks the checked boxes and updates the list
        holder.binding.floorCheckBox.setOnCheckedChangeListener {   buttonView, isChecked ->
            if (buttonView.isChecked) {
                Timber.d("${buttonView.text} checked")
                checkedFloors.add(buttonView.text.toString())
            } else if (!buttonView.isChecked) {
                Timber.d("${buttonView.text} unchecked")
                checkedFloors.remove(buttonView.text)
            }
            unique = checkedFloors.distinct().sorted()
            Timber.d("List: $unique")
        }
    }
    
    fun returnList(): List<String> {
        Timber.d("$unique")
        return unique
    }

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

    @SuppressLint("NotifyDataSetChanged")
    fun getAllFloors(floorsReceived: List<String>) {
        Timber.d("Floors received : $floorsReceived")
        this.floors = floorsReceived
        notifyDataSetChanged()
    }
}

Fragment code where I am trying to read it

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

        //Chosen Floors
        val chosenFloors = floorProfileDialogAdapter.returnList()
        Timber.d("Chosen floors : $chosenFloors")
}

Note: The list I am trying to receive is var unique: List<String> = mutableListOf. I tried to get it using the returnList() but the log in that function shows that list is empty. Similarly the Log in fragment shows that it received an empty list.

Edit 1 : Class to fill the Adapter Floors using getAllFloors()

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

        val floorList: MutableList<String> = mutableListOf()

        var profileName: String? = ""
        profileName = args.profileName
        
        //Profile name received
        Timber.d("Profile name : $profileName")

        //Getting list of all floors
        createProfileViewModel.totalFloors.observe(viewLifecycleOwner) {
            Timber.d("List of floors received : $it")

            val intList = it.map(String::toInt)
            val maxFloorValue = intList.last()
            var count = 0

            try {
                while (count <= maxFloorValue) {
                    floorList.add(count.toString())
                    count++
                }
            } catch (e: Exception) {
                Timber.d("Exception: $e")
            }
            floorProfileDialogAdapter.getAllFloors(floorList)
            Timber.d("Floor List : $floorList")
        }
oyeraghib
  • 878
  • 3
  • 8
  • 26
  • where is the class when you create an instance of that adapter at the first? when you fill the list on your adapter with `getAllFloors` function – galihif Aug 27 '22 at 13:27
  • Use a `SparseBooleanArray` to handle item selection in Adapters. – Darshan Aug 27 '22 at 13:53
  • @galihif I have updated the question. Though that function is not required to solve this issue, as far as my understanding goes. – oyeraghib Aug 27 '22 at 13:55
  • @DarShan I think the item selection is working fine. I am having the logs of when I have items selected and not selected. Also using this log statement `unique = checkedFloors.distinct().sorted()` `Timber.d("List: $unique")` I am able to see the selected list of checkboxes. But the issue is that the variable `unique` is not being updated outside `onBindViewHolder()` and remains like an empty string only. If I am missing out on something but not sure how `SpareseBooleanArray` could solve this, – oyeraghib Aug 27 '22 at 13:59

1 Answers1

1

When you first set up your Fragment and create your Adapter, unique is empty:

var unique: List<String> = mutableListOf()

(if you have some checked state you want to save and restore, you'll have to initialise this with your checked data)

In onViewCreated, during Fragment setup, you get a reference to this (empty) list:

// Fragment onViewCreated
val chosenFloors = floorProfileDialogAdapter.returnList()

// Adapter
fun returnList(): List<String> {
    return unique
}

So chosenFloors is a reference to this initial entry list. But when you actually update unique in onBindViewHolder

unique = checkedFloors.distinct().sorted()

you're replacing the current list with a new list object. You're not updating the existing list (even though you made it a MutableList). So you never actually add anything to that empty list you started with, and chosenFloors is left pointing at a list that contains nothing, while the Adapter has discarded it and unique holds a completely different object.


The solution there is to make unique a val (so you can't replace it) and just change its contents, e.g.

unique.clear()
unique += checkedFloors.distinct().sorted()

But I don't feel like that's your problem. Like I pointed out, that list is initially empty anyway, and you're grabbing it in your Fragment during initialisation just so you can print out its contents, as though you expect it to contain something at that point. Unless you initialise it with some values, it's gonna be empty.

If you're not already storing/restoring them, you'll need to handle that! I posted some code to do that on another answer so I'll just link that instead of repeating myself. That code is storing indices though, not text labels like you're doing. Indices are much cleaner and avoid errors - the text is more of a display thing, a property of the item the specific (and unique) index refers to. (But you can store a string array in SharedPreferences if you really want to.)


Also you're not actually updating your ViewHolder to display the checked state for the current item in onBindViewHolder. So whatever ViewHolder you happen to have been given (there's only a few of them for the list, they get reused) it's just showing whatever its checkbox was last set to, by you poking at it. Check an item, then scroll the list and see what happens!

So you need to check or uncheck the box so it's correct for the item you're displaying. This is pretty easy if you're storing the checked items by indices:

// explicitly set the checked state, depending on whether the item at 'position' is checked
holder.binding.floorCheckBox.checked = checkedItems[position]

You can work out something similar for your text label approach, but again I wouldn't recommend doing things that way.

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • Thanks @cactustictacs for explaining the answer so well. It does make sense why I was not able to receive the list. Though I was able to fix it very simply by passing the selected floors list as an argument. As my adapter is attached to a dialog fragment and I can access this `unique` value in that fragment using `onSubmitButton()` when I select the list of floors. And then I passed that list as an argument. Thanks for providing that answer as well, because maybe in the future I would need to implement it. – oyeraghib Aug 28 '22 at 14:19