본문 바로가기
안드로이드

[안드로이드] 리싸이클러 뷰 개선기 (코드 특이하게 쓰네 )

by keel_im 2021. 6. 15.
반응형

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 를 상속하는 것이 나쁜 패턴이 절대 아닙니다. 

반응형

댓글