即使列表的地址发生变化,属性的地址值是否保持不变?
Even if the address of the list changes, does the address value of the property remain the same?
目前,我正在 Android
中执行一项任务,根据 toggle button
更改 list
的 unit value
并显示具有更改值的列表。
我正在使用 ViewModel
和 LiveData
观察列表。
所以我使用 toList()
到 return a new list
并覆盖 old list
来观察值。
但是,即使 return 编辑了新列表,屏幕也不会更新。
我试过调试,但得到了一些难以理解的结果。
很明显,旧链表和新链表的地址值是不一样的,但是连old list
的unit
都变了
发生了什么事?
即使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 }
(实际上应该是 forEach
,map
创建一个您正在丢弃的新列表)时,您正在修改 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) }
目前,我正在 Android
中执行一项任务,根据 toggle button
更改 list
的 unit value
并显示具有更改值的列表。
我正在使用 ViewModel
和 LiveData
观察列表。
所以我使用 toList()
到 return a new list
并覆盖 old list
来观察值。
但是,即使 return 编辑了新列表,屏幕也不会更新。
我试过调试,但得到了一些难以理解的结果。
很明显,旧链表和新链表的地址值是不一样的,但是连old list
的unit
都变了
发生了什么事?
即使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 }
(实际上应该是 forEach
,map
创建一个您正在丢弃的新列表)时,您正在修改 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) }