即使列表的地址发生变化,属性的地址值是否保持不变?

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

目前,我正在 Android 中执行一项任务,根据 toggle button 更改 listunit value 并显示具有更改值的列表。

我正在使用 ViewModelLiveData 观察列表。

所以我使用 toList() 到 return a new list 并覆盖 old list 来观察值。

但是,即使 return 编辑了新列表,屏幕也不会更新。

我试过调试,但得到了一些难以理解的结果。

很明显,旧链表和新链表的地址值是不一样的,但是连old listunit都变了

发生了什么事?

即使List的地址不同,是否因为属性引用同一个地方,所以旧列表和新列表的值同时变化?


我给你看最简单的代码。

片段

// 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
)

适配器

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

资源库

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

您需要 post 您的观察者代码,以寻求有关它为何不更新的任何帮助。

至于奇怪的行为,setInfoList 包含一些 WorkoutSetInfo 对象,对吧?我们称它们为 A、B 和 C。当您调用 setInfoList.toList() 时,您正在创建一个新的 容器,它包含对对象 A、B 和 C 的相同引用。因为它是一个单独的列表,您可以在不影响原始列表的情况下添加和删除项目,但是对两个共享的 objects 的任何更改都将反映在两个列表中 - 因为它们都在查看同样的事情。

因此,当您执行 setInfoList.map { setInfo -> setInfo.unit = unit }(实际上应该是 forEachmap 创建一个您正在丢弃的新列表)时,您正在修改 A、B 和 C。所以您创建的包含这些对象的每个列表都会看到这些更改,包括您的旧列表。

基本上,如果您希望每个列表都是独立的,当您修改列表时,您需要创建项目的新实例,这意味着复制您的 WorkoutSetInfo 对象来创建新的对象,而不是更新当前的对象那些。如果它是 data class 那么你可以很容易地做到这一点(只要你没有需要自我复制的嵌套对象):

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

您不再需要在 getList 上执行 toList(),因为您只是传递列表的当前版本,并且该列表永远不会改变(因为您将创建一个新的)。这意味着你不需要那个函数,你可以只做 setInfoList public - 因为我把它改成了 listOf 来创建一个不可变的 List,它可以安全地传递因为无法修改。


该列表中的 WorkoutSetInfo 对象 仍然可以 进行外部修改(例如,通过更改其中一项的 unit 值),因此改为当您调用 changeUnit 时制作新副本,您可能希望在调用 getList 时执行此操作:

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

现在所有调用 getList 的东西都会得到一个包含唯一对象的唯一列表,因此它们彼此分开。如果你实际上不需要存储当前的 unit 值,你可以将它传递给 getList 而不是 changeUnit 函数:

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