Search code examples
kotlinandroid-recyclerviewandroid-arrayadapter

Add data dynamically in recyclerview kotlin


please I'm trying to dynamically add a textitem via an EditText by clicking a button in a Recyclerview, but when I click the button to add the text in the Array related to the Adapter and I apply the adapter.notifyDataSetCh.anged() the recycleview does not update and no longer detects click

mainActivity.kt

ackage com.example.recycleviewwork

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.View.OnClickListener
import android.widget.Button
import android.widget.EditText
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class Travel : AppCompatActivity(), View.OnClickListener {
    var check_travel_list = arrayOf<String>("cni", "psp", "hhn", "ddp", "ppe", "leh")
    var state_check_bool = arrayOf<Boolean>(false, false, false, false,false,false)
    val adapter = travel_list_adapter(check_travel_list,state_check_bool, this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_travel)
        var textEditValue = ""
        // RECYCLEVIEW Travel page
        val recview_travel = findViewById<RecyclerView>(R.id.travel_recview)
        recview_travel.layoutManager = LinearLayoutManager(this)
         recview_travel.adapter = adapter

        //EDIT text
        val edit_text_view = findViewById<EditText>(R.id.textedit)

        // ADD travel button
        val button_add_travel = findViewById<Button>(R.id.button_travel)
        button_add_travel.setOnClickListener()
        {
            textEditValue = edit_text_view.text.toString()
            check_travel_list += textEditValue
            state_check_bool += false
            adapter.notifyDataSetChanged()
            println(textEditValue)

        }

        }





    override fun onClick(vue: View) {
        val index = vue.tag as Int
        //changement des bool de couleur
        if(index != null)
        {
            state_check_bool[index] =  !state_check_bool[index]
            adapter.notifyDataSetChanged()
        }


    }
}

Adapter.kt

package com.example.recycleviewwork

import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView

 class travel_list_adapter(val travel_check_list : Array <String>,val travel_state_check : Array<Boolean> , val travel_click : View.OnClickListener) : RecyclerView.Adapter<travel_list_adapter.ViewHolder>()
{
    class ViewHolder (itemview: View ): RecyclerView.ViewHolder(itemview)
    {
       val cartview = itemview.findViewById<CardView>(R.id.cartview_travel_list)
        val text_check = itemview.findViewById<TextView>(R.id.text_travel_list)
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): travel_list_adapter.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view_item = inflater.inflate(R.layout.travel_list,parent,false)
        return ViewHolder(view_item)
    }



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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val current_check = travel_check_list[position]
        val state_state_check = travel_state_check[position]
        holder.cartview.setOnClickListener(travel_click)
        holder.cartview.tag = position
        holder.text_check.text = current_check
        if(state_state_check == true)
        {
        holder.text_check.setTextColor(Color.parseColor("#8BC34A"))
            }
        else
        {
            holder.text_check.setTextColor(Color.parseColor("#000000"))
        }

    }


}

However, by changing the value of a text in the Array linked to Adapter without changing the array lenght , the recycleview displays the change normally.


Solution

  • You're not updating the array in your Activity, you're replacing it with +=. That creates a new array with the extra element added, and assigns that to your check_travel_list variable.

    You start off with the first array object, and you pass that to your Adapter so both the Activity and Adapter are looking at the exact same object. If you made changes to that object, both places would see it. That's why changing the text on an existing element works - you're modifying the Array that the Adapter is also using, so it sees that change.

    But once you replace check_travel_list in the Activity with the second array object (created with +=) then they're no longer looking at the same thing. The Adapter still holds a reference to the original array, which hasn't changed.


    So two things - first, use a List instead of an Array. They're way easier to work with, you can add and remove items without needing to worry about empty spaces or the length (this is why adding another element creates a whole new array - arrays are fixed-length) and they're just the idiomatic way of holding a sequence of items in Kotlin. Only use an array if you actually need one for a specific reason (e.g. you do want the empty spaces*.

    With a list, you could just do this:

    // needs to be a MutableList if you need to modify it
    val check_travel_list = mutableListOf("cni", "psp", "hhn", "ddp", "ppe", "leh")
    
    ...
    
    check_travel_list.add("something else")
    

    Now you're modifying the existing list object, and your Adapter (which is looking at the same list) will see the change too.

    Also note that check_travel_list is a val, which means it can't have a new list assigned to it - this avoids bugs like the one you're getting. By making it val by default, if you do decide to change that to a var you need to think about why you need to reassign it with different list object, and it forces you to think about the consequences of doing that. Using val unless you need to modify it is safer, same as with the List/MutableList thing!


    The other thing is that I'd recommend not sharing the list in the first place! When you have two completely separate things sharing the same data object, it can get confusing when one of them starts modifying it, or makes changes it expects the other user to see, but with no clear way of pushing those changes.

    So it's safer to keep them completely separate. Instead of sharing a list object, let the Adapter manage its own state. Create a function like fun setData(data: List<String>) that updates the Adapter's own internal data, and runs the appropriate notify* function itself. Nothing on the outside should be concerned with what's happening in the Adapter internally, y'know?

    // passing in initial data - note this isn't a property (val/var), it's just temporary
    class MyAdapter(initialData: List<String>) {
    
        // this is what actually holds the adapter's data
        var internalData: List<String> = emptyList()
    
        init {
            // initialise the internal data set
            setData(initialData)
        }
    
        fun setData(data: List<String>) {
            // calling toList() makes a new copy of the list, that can't be modified
            // by anything externally - you can only update the adapter data through this function!
            internalData = data.toList()
            // now update using an appropriate method - the adapter can be smart about it
            // depending on what's changed (or it could be using a DiffUtil)
            notifyDataSetChanged()
        }
    
    }
    

    Then you update the RecyclerView just by calling setData with your updated data. And that way it doesn't matter if it's the same list, or you made a new copy, or whatever - all the adapter cares about is getting some data and updating itself to store and display it.

    You don't need the initialData and init stuff if you don't want, you could just call setData after creating the Adapter object to initialise it. But this is how you'd do it during construction if you really wanted!