1200 분이 읽어 주셔서 항상 감사드립니다. 100분마다 감사 인사를 꼭 하는데 항상 새롭네요..
오늘은 그냥 제가 피드백을 받으면서 슬픈 피드백을 받아서 겪은 얘기를 조금 하려고 합니다.
일단 오늘의 문제 코드
class SearchRecyclerViewAdapter(private var values: List<NandaEntity>) :
RecyclerView.Adapter<SearchRecyclerViewAdapter.ViewHolder>() {
init {
setHasStableIds(true) //고유 id 를 설정
}
override fun getItemId(position: Int): Long = position.toLong()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemListviewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
), listener
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
holder.itemTextView.text = item.diagnosis
holder.desView.text = item.reason
holder.classView.text = item.class_name
holder.domainView.text = item.domain_name
}
override fun getItemCount(): Int = values.size
fun getItem(postion: Int): NandaEntity = values[postion]
fun setNandaItem(items: List<NandaEntity>) {
val disposable = Observable.just(items)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.map { DiffUtil.calculateDiff(ListDiffCallback(this.values, items)) }
.subscribe({
this.values = items
it.dispatchUpdatesTo(this)
}, {
})
disposable.dispose()
}
interface OnSearchItemClickListener {
fun onSearchItemClick(position: Int)
fun onSearchItemLongClick(position: Int)
}
var listener: OnSearchItemClickListener? = null
inner class ViewHolder(binding: ItemListviewBinding, listener: OnSearchItemClickListener?) :
RecyclerView.ViewHolder(binding.root) {
val itemTextView: TextView = binding.diagnosisItem
val desView: TextView = binding.diagnosisDes
val classView = binding.className
val domainView = binding.domainName
init {
binding.root.setOnClickListener {
listener?.onSearchItemClick(adapterPosition)
}
binding.root.setOnLongClickListener {
listener?.onSearchItemLongClick(adapterPosition)
return@setOnLongClickListener true
}
}
fun bind(isActivated: Boolean = false) {
itemView.isActivated = isActivated
}
override fun toString(): String {
return super.toString() + " '" + desView.text + "'"
}
}
}
어떻게 보면 취미생활이긴 한데 저는 프로젝트를 계속해서 유지해서 발전하는 것을 좋아합니다. 오늘은 제가 유지하고 있는 프로젝트를 보는데 Adapter 코드가 참 재밌게 짠 것을 보았습니다. 이걸로 피드백을 받았는데. 참.. 생각이 많이 들더라구요
아주 초창기에 작성했었는데. 여러가지 짬뽕을 해놨네요. 여러가지를 섞어 놓고 개념 정립도 제대로 되어 있지 않은 상태라 더 그런 것 같습니다.
일단 결론적으로 엉망입니다. 이 클래스의 문제점은 3가지 입니다.
1. bind 함수에 대한 이해가 제대로 되지 않았다.
2. selection 라이브러리를 쓰는 것 같은데 제대로 코드가 정리되어 있지 않다.
3. 무분별한 diffUtil 구현 입니다.
이 문제를 해결하기 위하서는 구조를 바꾸고 개선할 필요가 있었습니다. 저는 해결책을 상속하는 클래스를 바꾸었습니다. 바로 ListAdapter 입니다.
몰랐었는데 Recycler 에서 사용할 수 있는 adapter 가 또 있다는 것을 알고는 있었는데 막상 쓰려니 어려웠습니다. 먼저 코드를 보면
/*
* Designed and developed by 2020 keelim (Jaehyun Kim)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.keelim.nandadiagnosis.ui.main.search
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.selection.ItemDetailsLookup
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.keelim.nandadiagnosis.data.db.NandaEntity
import com.keelim.nandadiagnosis.databinding.ItemListviewBinding
class SearchRecyclerViewAdapter2(
val favoriteListener: (Int, Int) -> Unit,
) :
ListAdapter<NandaEntity, SearchRecyclerViewAdapter2.ViewHolder>(diffUtil) {
var tracker: SelectionTracker<Long>? = null
var listener: OnSearchItemClickListener? = null
init {
setHasStableIds(true) // 고유 id 를 설정
}
override fun getItemId(position: Int): Long = position.toLong()
public override fun getItem(position: Int): NandaEntity = currentList[position]
inner class ViewHolder(private val binding: ItemListviewBinding, listener: OnSearchItemClickListener?) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: NandaEntity, isActivated: Boolean = false) {
itemView.isActivated = isActivated
binding.diagnosisItem.text = item.diagnosis
binding.diagnosisDes.text = item.reason
binding.className.text = item.class_name
binding.domainName.text = item.domain_name
binding.searchSwitch.isChecked = item.favorite == 1
binding.root.setOnClickListener {
listener?.onSearchItemClick(bindingAdapterPosition)
}
binding.root.setOnLongClickListener {
listener?.onSearchItemLongClick(bindingAdapterPosition)
return@setOnLongClickListener true
}
binding.searchSwitch.setOnClickListener {
favoriteListener(item.favorite, item.nanda_id)
}
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition(): Int = bindingAdapterPosition
override fun getSelectionKey(): Long = itemId
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemListviewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
listener
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
tracker?.let {
holder.bind(currentList[position], it.isSelected(position.toLong()))
}
}
interface OnSearchItemClickListener {
fun onSearchItemClick(position: Int)
fun onSearchItemLongClick(position: Int)
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<NandaEntity>() {
override fun areItemsTheSame(oldItem: NandaEntity, newItem: NandaEntity): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: NandaEntity, newItem: NandaEntity): Boolean {
return oldItem.diagnosis == newItem.diagnosis
}
}
}
}
- 먼저 해준 일은 Viewholder 안에서 fun bind() 함수를 제대로 작성을 해주는 것이였습니다. 확실히 이전 코드보다 깔끔해진 것을 볼 수 있었습니다. 항상 init 왜 썼는지 이해되지 않았는데, 확실히 쓸 필요가 없었습니다.
- diffUtil 을 companion object 로 빼줘서 클래스를 선언할때 넣어주었습니다.
- 클릭 이벤트를 관리를 할 때, 인터페이스를 많이 사용했는데, 고차 함수를 쓰는 법을 알고 나서 상당히 활용 중입니다. 확실히 코드가 깔끔해진 느낌을 상당히 많이 받았습니다. 아직 수정 중이긴 합니다.
결론
- 블로그를 많이 참조하지 말고 Sunflower 를 많이 보자.
- 그리고 Recycler.ViewHolder 를 상속하는 것이 나쁜 패턴이 절대 아닙니다.
'안드로이드' 카테고리의 다른 글
[안드로이드] Flow를 정말 간단히 먼저 사용해보자. (0) | 2021.08.01 |
---|---|
[안드로이드] 그래 이게 UseCase 야. (2) | 2021.06.27 |
[안드로이드] Android Studio 2021.1.1 Canary Bumblebee gradle error (0) | 2021.05.30 |
[안드로이드] 앱 오프닝 광고 구현 [최근 업데이트] (0) | 2021.04.18 |
[안드로이드] 멀티 모듈 공통 gradle 그리고 ktlint 적용을 해보자 (0) | 2021.04.14 |
댓글