Home > front end >  Kotlin recycler views
Kotlin recycler views

Time:09-25

I am hoping someone can help me understand how the recycler works so that I can manage the data properly.

I have two recyclers that get a list of tasks (currently 6) and a string that create an item for each letter (for example, StoneAge creates 8 items). The recyclers themselves work just fine. Both display the lists correctly, however, I think the problem I have with both of them boils down to the same thing.

The tasks recycler highlights the issue best. I want each task to display differently based on the value of Status that comes back in the set. So, if my set is Task A (Complete), Task B (Started), Task C (Not started) and Task D (Complete) I am trying to set the colour of the title differently for each status. I get the status returned into the set for the recycler and can display the correct values with each task, but when try to apply this to the textView using:

override fun onBindViewHolder(holder: ViewHolder, position : Int) {
  val t = mList[position]

  holder.txtTaskName.text = t.taskName
  holder.txtTaskName.setTextColor(t.bgColor!!)
}

It sets everything to the same colour (the colour of the last one in the list).

I understand that the recycler reuses instances of the layout and I read a similar answer here saying that I need to check the position. I'm not sure I've understood quite how this works since I am already using the position from the set.

I think this is the same reason I have a problem with the second recycler for the string. What it does is create 8 items, one for each letter of the word (StoneAge) along with an EditText for the user to enter a word(s) beginning with each letter (an acrostic poem). When the view scrolls the text is populated with a previous value and some weird stuff is happening with the input. I suspect this is related to the fact that I don't properly understand how the recycler is reusing the items. I would assume that since it's got the correct task (t) and so knows the correct bgColor, it would be able to set it.

Could someone provide a simple example/explanation as to how I handle the position properly please?

OK. This is the code from MainActivity (I've removed a few bits that aren't anything to do with the recycler since it was massive)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//Definitions
        allTasksRecycler = findViewById<RecyclerView>(R.id.allTasksRecycler)
        btnAddTask = findViewById(R.id.btnAddTask)           
        lblUserTaskCount = findViewById(R.id.lblUserTaskCount)

//Recycler
        allTasksRecycler.layoutManager = LinearLayoutManager(this)

//Firebase Queries
        val data = ArrayList<ItemsViewModel>()
        var arrUserTasks: ArrayList<String>
        var arrUserTaskComplete: ArrayList<Boolean>
        var arrUserTaskStarted: ArrayList<Boolean>
        var bgColor: Int = 0
        var utID: Int

        db.collection("tasks")
            .get()
            .addOnSuccessListener { result ->
                totalTasks = result.count()
                availableTasks = totalTasks

                db.collection("userTask")
                    .whereEqualTo("userID", HWG.FBUserID)
                    .get()
                    .addOnSuccessListener { uts ->
                        if (uts.count() > 0) {
                            arrUserTasks = ArrayList(uts.count() - 1)
                            arrUserTaskComplete = ArrayList(uts.count() - 1)
                            arrUserTaskStarted = ArrayList(uts.count() - 1)

                            for (ut in uts) {
                                Log.d(HWG.TAG, "Reading ${ut.data.getValue("taskID").toString()}....")
                                arrUserTasks.add(ut.data.getValue("taskID").toString())
                                if (ut.data.getValue("isComplete") == null) {
                                    arrUserTaskComplete.add(false)
                                } 
                                else {  
arrUserTaskComplete.add(ut.data.getValue("isComplete") as Boolean)
                                }
                                if (ut.data.getValue("startDate") == null) {
                                    arrUserTaskStarted.add(false)
                                } else {
                                    arrUserTaskStarted.add(true)
                                }
                            }

                            for (document in result) {
                                utID = -1

                                Log.d(HWG.TAG,"Looking for ${document.data.getValue("name").toString()} {${arrUserTasks.size.toString()}}...")

                                utID = arrUserTasks.indexOf(document.data.getValue("name").toString())

                                if (utID >= 0) {
                                    if (arrUserTaskComplete[utID] == true) {
                                        bgColor = R.color.Completed
       
                         } else if (arrUserTaskStarted[utID] == true) {
                                    bgColor = R.color.Started
                                }
                            } else {
                                bgColor = R.color.white
                            }

                            data.add(
                                ItemsViewModel(
                                        
document.data.getValue("name").toString(),                                
document.data.getValue("description").toString(),
document.data.getValue("subject").toString(),
document.data.getValue("subjectID").toString().toInt(),
document.data.getValue("requirements").toString(),
document.data.getValue("evidence").toString(),
document.data.getValue("evidenceType").toString(),
document.data.getValue("taskType").toString(),
bgColor
                                )
                            )

                            //val adapter = AllTasksAdapter(data)
                            val adapter = AllTasksAdapter(this, data)
                            allTasksRecycler.adapter = adapter
                        }
                    }
                }
                .addOnFailureListener { }

        }
        .addOnFailureListener { exception ->
            Log.d(TAG, "Error getting documents: ", exception)
        }
}

This is the Adapter:

class AllTasksAdapter(val context: Activity, private val mList: List<ItemsViewModel>):RecyclerView.Adapter<AllTasksAdapter.ViewHolder>() {

    class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
        val subjectImage: ImageView = itemView.findViewById(R.id.subjectImage)
        val txtTaskName: TextView = itemView.findViewById(R.id.txtTaskName)
        val txtTaskRequirements: TextView = itemView.findViewById(R.id.txtTaskRequirements)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // inflates the card_view_design view that is used to hold list item
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.card_view_design, parent, false)

        return ViewHolder(view)
    }

    @SuppressLint("ResourceAsColor")
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        val t = mList[position]

        // sets the views from our itemHolder class
        if(t.subjectID == 1) //English/Topic
        {   holder.subjectImage.setImageResource(R.drawable.icons8_knowledge_sharing_80)  }
        else if(t.subjectID == 2) //Maths
        {   holder.subjectImage.setImageResource(R.drawable.icons8_maths_64)  }
        else if(t.subjectID == 3) //Science
        {   holder.subjectImage.setImageResource(R.drawable.icons8_test_tube_80)  }
        else if(t.subjectID == 4) //Reading
        {   holder.subjectImage.setImageResource(R.drawable.icons8_reading_80)  }
        else if(t.subjectID == 5) //Spelling
        {   holder.subjectImage.setImageResource(R.drawable.icons8_broken_pencil_80)  }

        holder.txtTaskName.text = t.name
        holder.txtTaskRequirements.text = t.requirements
        holder.subjectImage.tooltipText = "${t.subject} (${t.bgColor})"
        holder.txtTaskName.setTextColor(t.bgColor!!)    
        holder.itemView.setOnClickListener {
            CustomDialog(this, t).show()
        }
    }

    // return the number of the items in the list
    override fun getItemCount(): Int {
        return mList.size
    }
}

I've removed the code for CustomDialog as this isn't related (and makes the code even messier!)

CodePudding user response:

the problem of repeating the last color is not about the way RecyclerView working, it is about the data that you are providing for the adapter. in the MainActivity class you have defined a bgColor variable and you are passing this same variable to all of ItemsViewModel items in your recycler view. bgColor is an object with one reference that is passed to all items of your RecyclerView. You are changing its value every time you add it to list but remember it is still the same object. so when you change the value of this object the color of all items in your list will change because the color of all of them is still pointing to bgColor and you have changed the value of bgColor. so when You add the last ItemsViewModel to the list and you change the bgColor for last time, the color of all of items in list will change to last bgColor value.

for fixing this you only need to define the bgColor inside of your for loop so for every item in the list it defines a new object with different references.

      for (document in result) {

                        var bgColor: Int = 0
                        ....
  • Related