마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

토글값에 따른 텍스트뷰의 변환을 하려는데..

0 추천

 

사진과 같은 형태에서 토글버튼(KG, LBS)의 선택에 따른 아래 리사이클러뷰 아이템의 가운데 Unit 값을 업데이트하여 설정하려는데 버튼을 눌러도 뷰가 업데이트 되지 않습니다. (사진속에서는 현재 가운데 EditText 끝에 kg 및 lbs 단위가 붙어야하는데 해결되지않아 보이지 않습니다)

ViewModel 클래스내에서 list가 litems를 get()하여 같은 주소를 공유하다보니 list의 unit값을 변경해도 items까지 같이 변화해서 문제인건가해서 혹시나해서 id까지 같이바꾸어주는데도 이러한 현상이 발생하네요..

 

DiffUtil을 사용해서 값을 바꾸어주고 있는데 디버깅해보니 이상하게 DiffUtil에 브레이킹포인트가 동작안하는거보니 이것문제같기도한데 어느 부분을 놓치고 있는지 잘모르겠어요.

 

RoutineItem

sealed interface RoutineItem {
    val id: String

    data class Header(
        override val id: String = UUID.randomUUID().toString(),
        val workout: String,
        val unit: String,
    ) : RoutineItem

    data class Detail(
        override var id: String = UUID.randomUUID().toString(),
        var unit: String = "",
        val set: Int,
        var weight: String = "",
        var reps: String = "",
    ) : RoutineItem
}

DiffCallBack

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

    override fun areContentsTheSame(
        oldItem: RoutineItem2.Detail,
        newItem: RoutineItem2.Detail
    ): Boolean {
        return oldItem == newItem
    }
}

ViewModel

class DetailViewModel : ViewModel() {
    private val _items: MutableLiveData<List<RoutineItem2.Detail>> = MutableLiveData()
    val items = _items
    private val list: List<RoutineItem2.Detail>
        get() = _items.value ?: emptyList()

    fun changeUnit(unit: String) {
        list.forEach {
            it.id = UUID.randomUUID().toString()
            it.unit = unit
        }
        _items.postValue(list)
    }

    fun addDetail() {
        val item = RoutineItem2.Detail(set = list.size+1)
        _items.postValue(list.plus(item)) // plus를 사용하면 새로운 List가 반환

    }

    fun deleteDetail() {
        _items.postValue(list.dropLast(1))
    }
}

Fragment

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = FragmentWriteDetailBinding.inflate(inflater, container, false)

    adapter = DetailAdapter()
    binding.rv.adapter = adapter
    binding.apply {
        rv.adapter = adapter
        rv.itemAnimator = null

        header.workout.text = args.workout

        header.add.setOnClickListener {
            vm.addDetail()
        }
        header.delete.setOnClickListener {
            vm.deleteDetail()
        }
        header.toggleButton.addOnButtonCheckedListener { _, checkedId, _ ->
            when(checkedId) {
                R.id.kg -> vm.changeUnit("kg")
                R.id.lb -> vm.changeUnit("lbs")
            }
        }
    }
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    vm.items.observe(viewLifecycleOwner) { newList ->
        adapter.submitList(newList) // 새로운 리스트를 넘김.
    }
}

 

codeslave (3,940 포인트) 님이 2022년 3월 12일 질문

1개의 답변

0 추천

DiffUtil의 구현체인 AsyncListDiffer.java의 소스코드를 보면 아래와 같이 == 비교를 제일 먼저 수행하도록 되어 있습니다.
같은 인스턴스일 경우는 아이템을 비교하지 않습니다.

public void submitList(@Nullable final List<T> newList, @Nullable final Runnable commitCallback) {
    final int runGeneration = ++mMaxScheduledGeneration;
     if (newList == mList) {  // => 요기서 같은 오브젝이면 바로 리턴합니다.
           if (commitCallback != null) {
                 commitCallback.run();
           }
           return;
      }

       final List<T>  previousList = mReadOnlyList;
       ...
    }
}


따라서 같은 인스턴스일 경우는 리스트 멤버를 추가/수정/삭제를 하더라도 리사이클러뷰가 갱신되지 않습니다.

따라서 해결책은 Detail 클래스에서 var멤버를 val로 바꾼후 아이템을 복사하시거나 submitList하실 때 새로운 리스트를 만들어서 넘기거나 하는 게 좋을 것 같습니다. val 멤버를 사용할 경우, 

fun changeUnit(unit: String) {
    val updatedList = list.map { it.copy(unit = unit) }
    _items.postValue(updatedList)    
}

위처럼 changeUnit 함수 안에서  map과  copy를 이용하여 unit이 업데이트 된 리스트를 리턴할 수 있습니다.

따라서,  Kotlin data class를 사용할 때는 이런 부작용을 방지하기 위해서 가급적이면 val을 사용하고 copy를 이용하여 프로퍼티를 변경하는 게 안전합니다.

한번 시도해 보시죠.

spark (227,830 포인트) 님이 2022년 3월 12일 답변
spark님이 2022년 3월 13일 수정
...