Home > Blockchain >  Even if the address of the list changes, does the address value of the property remain the same?
Even if the address of the list changes, does the address value of the property remain the same?

Time:05-13

Currently, I am making a task in Android that changes the unit value of the list according to the toggle button and shows the list with the changed value.

I am observing the list using a ViewModel and LiveData.

So i use toList() to return a new list and overwrite the old list to observe the values.

However, the screen is not updated even though it has returned a new list.

I've tried debugging and I'm getting some incomprehensible results.

Obviously, the address values ​​of the old list and the new list are different, but even the unit of the old list has changed.

What happened?

Even if the addresses of Lists are different, do the values ​​of the old list and the new list change at the same time because the properties refer to the same place?


I'll show you the minimal code.

Fragment

// Change Unit
toggleButton.addOnButtonCheckedListener { _, checkedId, isChecked ->
    if(isChecked) {
        when(checkedId) {
            R.id.kg -> vm.changeUnit("kg")
            R.id.lb -> vm.changeUnit("lbs")
        }
    }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    vm.items.observe(viewLifecycleOwner) { newList ->
        adapter.submitList(newList)
    }
}

WorkoutSetInfo

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Workout::class,
            parentColumns = arrayOf("workoutId"),
            childColumns = arrayOf("parentWorkoutId"),
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class WorkoutSetInfo(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val set: Int,
    var weight: String = "",
    var reps: String = "",
    var unit: String = "kg",
    val parentWorkoutId: Long = 0
)

Adapter

class DetailAdapter
    : ListAdapter<WorkoutSetInfo, DetailAdapter.ViewHolder>(DetailDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            ItemRoutineDetailBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(currentList[position])
    }

    inner class ViewHolder(val binding: ItemRoutineDetailBinding) : RecyclerView.ViewHolder(binding.root) {
        private var weightTextWatcher: TextWatcher? = null
        private var repTextWatcher: TextWatcher? = null

        fun bind(item: WorkoutSetInfo) {

            binding.set.text = item.set.toString()
            binding.weight.removeTextChangedListener(weightTextWatcher)
            binding.unit.text = item.unit
            binding.rep.removeTextChangedListener(repTextWatcher)

            weightTextWatcher = object : TextWatcher {
                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
                override fun afterTextChanged(w: Editable?) {
                    if(!binding.weight.hasFocus())
                        return
                    item.weight = w.toString()
                }
            }

            repTextWatcher = object : TextWatcher {
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
                override fun afterTextChanged(r: Editable?) {
                    if(!binding.rep.hasFocus())
                        return
                    item.reps = r.toString()
                }
            }

            binding.apply {
                weight.setTextIfDifferent(item.weight)
                weight.addTextChangedListener(weightTextWatcher)
                rep.setTextIfDifferent(item.reps)
                rep.addTextChangedListener(repTextWatcher)
            }
        }
    }
}

DiffUtil*

class DetailDiffCallback : DiffUtil.ItemCallback<WorkoutSetInfo>() {
    override fun areItemsTheSame(
        oldItem: WorkoutSetInfo,
        newItem: WorkoutSetInfo
    ): Boolean  {
       return (oldItem.id == newItem.id)
    }

    override fun areContentsTheSame(
        oldItem: WorkoutSetInfo,
        newItem: WorkoutSetInfo
    ): Boolean {
        return oldItem == newItem
    }
}

ViewModel

class DetailViewModel(application: Application, title: String) : ViewModel() {
    private val workoutDao = DetailDatabase.getDatabase(application)!!.workoutDao()
    private val repository: WorkoutRepository = WorkoutRepository(workoutDao, title)
    private val _items: MutableLiveData<List<WorkoutSetInfo>> = MutableLiveData()
    val items = _items

    fun changeUnit(unit: String) {
        repository.changeUnit(unit)
        _items.postValue(repository.getList())
    }

    fun addSet() {
        viewModelScope.launch(Dispatchers.IO){
            repository.add()
            _items.postValue(repository.getList())
        }
    }

    fun deleteSet() {
        repository.delete()
        _items.postValue(repository.getList())
    }

    fun save() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.save()
        }
    }
}

Repository

class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
    private val workout = Workout(title = title)
    private val setInfoList = ArrayList<WorkoutSetInfo>()

    fun changeUnit(unit: String) {
        setInfoList.map { setInfo -> 
            setInfo.unit = unit
        }
    }
    
    fun add() {
        val item = WorkoutSetInfo(set = setInfoList.size   1)
        setInfoList.add(item)
    }

    fun delete() {
        if(setInfoList.size != 0)
            setInfoList.removeLast()
        return
    }

    fun save() {
        val workoutId = workoutDao.insertWorkout(workout)
        val newWorkoutSetInfoList = setInfoList.map { setInfo ->
            setInfo.copy(parentWorkoutId = workoutId)
        }
        workoutDao.insertSetInfoList(newWorkoutSetInfoList)
    }
    
    fun getList() : List<WorkoutSetInfo> = setInfoList.toList()
}

CodePudding user response:

You'd need to post your observer code for any help with why it's not updating.

As for the weird behaviour, setInfoList contains a few WorkoutSetInfo objects, right? Let's call them A, B and C. When you call setInfoList.toList() you're creating a new container, which holds the same references to objects A, B and C. Because it's a separate list, you can add and remove items without affecting the original list, but any changes to the objects that both share will be reflected in both lists - because they're both looking at the same thing.

So when you do setInfoList.map { setInfo -> setInfo.unit = unit } (which should be forEach really, map creates a new list you're discarding) you're modifying A, B and C. So every list you've made that contains those objects will see those changes, including your old list.

Basically if you want each list to be independent, when you modify the list you need to create new instances of the items, which means copying your WorkoutSetInfo objects to create new ones, instead of updating the current ones. If it's a data class then you can do that fairly easily (so long as you don't have nested objects that need copying themselves):

// var so we can replace it with a new list
private var setInfoList = listOf<WorkoutSetInfo>()

fun changeUnit(unit: String) {
    // create a new list, copying each item with a change to the unit property
    setInfoList = setInfoList.map { setInfo ->
        setInfo.copy(unit = unit)
    }
}

You don't need to do toList() on getList anymore, since you're just passing the current version of the list, and that list will never change (because you'll just create a new one). Meaning you don't need that function, you can just make setInfoList public - and because I changed it to listOf which creates an immutable List, it's safe to pass around because it can't be modified.


The WorkoutSetInfo objects inside that list could still be modified externally though (e.g. by changing one of the items' unit value), so instead of making a new copy when you call changeUnit, you might want to do it when you call getList instead:

class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
    private val workout = Workout(title = title)
    private val setInfoList = ArrayList<WorkoutSetInfo>()
    // store the current unit here
    private var currentUnit = "kg"

    fun changeUnit(unit: String) {
        currentUnit = unit
    }
    
    // return new List
    fun getList() : List<WorkoutSetInfo> = setInfoList.map { it.copy(unit = currentUnit) }
}

Now everything that calls getList gets a unique list with unique objects, so they're all separate from each other. And if you don't actually need to store the current unit value, you could pass it in to getList instead of having a changeUnit function:

fun getList(unit: String) = setInfoList.map { it.copy(unit = unit) }
  • Related