24.01.10 sealed class와 RecyclerViewType

KSang·2024년 1월 10일
0

TIL

목록 보기
30/101

sealed class

‘봉인된’ 이라는 의미로 무언가 안전하게 보관하기 위해 묶어 두는 것

sealed 키워드를 이용함

실드 클래스 그 자체로는 추상 클래스와 같아 인스턴스 생성 불가능

생성자가 기본적으로 private, private이 아닌 생성자 허용x

실드 클래스는 같은 파일 안에서는 상속이 가능

블록 안에 선언되는 클래스는 상속이 필요한 경우 open 키워드로 선언

 // 실드 클래스 선언 방법 첫번째
  
  sealed class Result{
    open class Success(val message: String): Result()
    class Error(val code :Int , val message: String): Result()
  }
  
  class Status: Result()  //  실드 클래스 상속은 같은 파일에서만 가능!
  class Inside: Result.Success("Status")  //  내부 클래스 상속
  // 실드 클래스 선언 방법 두번째
sealed class Result

open class Success(val message: String) : Result()
class Error(val code: Int, val message: String): Result()

class Status: Result()
class Inside: Success("Status")

  fun main(){
  val result = Result.Success("Good!")
  val msg = eval(result)
  println(msg)
}

fun eval(result: Result): String = when(result){
  is Status -> "in progress"
  is Result.Success -> result.message
  is Result.Error -> result.message
  //모든 조건을 가지므로 else가 필요가 없다! 다른 값이 들어오는 등 불안정한 상태를 방지할 수 있음
}

다시 정리하면

sealed 클래스는 자기 자신이 추상 클래스이고, 자신을 상속받는 여러 서브 클래스들을 가질 수 있다. 이를 사용하면 enum 클래스와 달리 상속을 지원하기 때문에, 상속을 활용한 풍부한 동작을 구현할 수 있다.

그리고 자신을 상속받는 서브 클래스의 종류를 제한할 수 있다. 왜냐하면 sealed 클래스는 다음과 같은 특성을 지니기 때문이다.

  • sealed 클래스의 서브 클래스들은 반드시 같은 파일 내에 선언되어야 함
  • 단, sealed 클래스의 서브 클래스를 상속한 클래스들은 같은 파일 내에 없어도 됨
  • sealed 클래스는 기본적으로 abstract 클래스임
  • sealed 클래스는 private 생성자만 갖게 됨

RecyclerViewType

리사이클러뷰에서 표기되는 항목들을 나타내는게 하는게 뭐가 있을까?

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
inner class ItemViewHolder(binding: ItemPersonalListBinding) : RecyclerView.ViewHolder(binding.root)

binding을 설정해주는 onCreateViewHolder

데이터를 보여주는 onBindViewHolder

그리고 위젯들을 관리하는 Holder가 있다

근데 이것들을 조건에 따라 바뀌게 한다면?

그러면 리사이클러뷰 하나로 다양한 아이템 레이아웃을 사용하고

다양하게 쓸 수 있지 않을까?

근데 데이터에 따라 아이템을 어떻게 설정을 할까?

과거 비슷한사례로 회원가입 유효성 검사가 떠오른다

enum class EditType {
    NAME, ID, EMAIL_ID, EMAIL, PASSWORD, PASSWORD_CHECK, NONE
}
...
//뷰모델
fun getErrorMessage(type: EditType, text: String) {
        _errors[type]?.value = errorMessage(type, text)
    }

enum class로 데이터의 타입들을 나눠주고, 어떤 editText에서 왔는지 뷰모델에서 View와 직접적으로 연결되면 안되니 Type을 정해서 text와 함께 보냈다

이것처럼 리사이클러뷰도 타입을 정해서 데이터를 보내주고

타입에 맞춰서 홀더와 뷰홀더등이 변하게 하면 되는것이다

앞서 sealed클래스를 설명했는데 여기서 enum class가 아닌 sealed클래스를 사용해

타입을 분류해주고 리사이클러뷰의 타입을 다양하게 쓸수 있다
-> 굳이 enum class를 사용하지 않고 왜 sealed class를 씀?
sealed class는 자식의 수가 정해져있다
그래서 when문 과 같은 메소드를 실행할때

sealed class ViewType {
    class Header()
    class Body()
    class Nav()
}
...
when(type) {
   Header -> 
   Body ->
   Nav ->
   }

이렇게 else없이 수행 할 수 있어서 NullpointExeption과 같은 에러를 예방할 수 있다!

다시 리사이클러뷰로 넘어가서

Adapter에서 타입을 companion object로 선언한다

    companion object {
        private const val ITEM_VIEW_TYPE_HEADER = 0
        private const val ITEM_VIEW_TYPE_ITEM = 1
    }
    
        override fun getItemViewType(position: Int): Int {
        return when (mItem[position]) {
            is ViewType.ItemHeader -> ITEM_VIEW_TYPE_HEADER
            is ViewType.Item -> ITEM_VIEW_TYPE_ITEM
        }
    }

그리고 getItemViewType을 오버라이드하면 sealed class 타입에 맞춰 타입을 정해준다

그냥 숫자를 써도 되긴하지만 가독성과 다른사람이 볼때 유지보수하기 좋게 이름을 적어준다

이런식으로 타입을 구분하면 리사이클러뷰 하나로 다양한 레이아웃을 표시할 수 있다.

실전으로 들어가보자

실전

,

일단 단순하게 시작하기에 적합해보이는

번개장터의 설정 부분이다.'

레이아웃은 스킵하고 로직부분만 적어보겠다

여기서 사용자설정부터 로그아웃까지 전부 적어주자

object PersonalList {
    val list = listOf(
        "사용자 설정-Header",
        "계정 설정",
        "알림 설정",
        "우리동네 지역 설정",
        "배송지 관리",
        "내 계좌 관리",
        "간편결제 관리",
        "차단 상점 관리",
        "서비스 정보-Header",
        "이용약관",
        "개인정보 처리방침",
        "위치기반 서비스 이용약관",
        "오픈소스 라이선스",
        "버전정보",
        "-Header",
        "로그아웃"
    )
}

버튼이 아닌 부분은 -Header로 표기해 구분해 줬다.

class ViewTypeUseCase {
    operator fun invoke(texts: List<String>): List<ViewType> {
        return texts.map { item ->
            if (item.contains("-Header")) {
                ViewType.ItemHeader(item.replace("-Header",""))
            } else {
                ViewType.Item(item)
            }
        }
    }
}

이 리스트를 text로 받는 클래스를 만들었고 contains를 이용해 -Header가 들어가 있는 문자를 구분 지워준뒤 sealed class로 데이터를 구분

inner class ItemViewHolder(binding: ItemPersonalListBinding) : RecyclerView.ViewHolder(binding.root){
        val imageArrow = binding.ivPersonalArrow
        val title = binding.tvPersonalTitle
    }
    inner class HeaderViewHolder(binding: ItemPersonalHeaderBinding) : RecyclerView.ViewHolder(binding.root){
        val title = binding.tvPersonalHeader
    }

어댑터에서는 각각 레이아웃에 따른 Holder를 만들어준다

이제 데이터를 넣으면

override fun getItemViewType(position: Int): Int {
        return when (mItem[position]) {
            is ViewType.ItemHeader -> ITEM_VIEW_TYPE_HEADER
            is ViewType.Item -> ITEM_VIEW_TYPE_ITEM
        }
    }

이런식으로 타입을 구분해주고

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType){
            ITEM_VIEW_TYPE_HEADER -> {
                val binding = ItemPersonalHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                HeaderViewHolder(binding)
            }
            else -> {
                val binding = ItemPersonalListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                ItemViewHolder(binding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = mItem[position]){
            is ViewType.ItemHeader -> {
                (holder as HeaderViewHolder).title.text = item.title
            }
            is ViewType.Item -> {
                (holder as ItemViewHolder).title.text = item.title
                holder.itemView.setOnClickListener {
                    itemClick?.onClick(it, position)
                }
            }
        }
    }

이런식으로 when문으로 서로 구분해서 수행하면 끝

좀더 신경쓰고 item만 많으면

이런식으로 구성할 수가 있다

만들면서 든 생각은

이거 현존하는 왠만한 앱들은 전부 코디네이터 레이아웃이랑 nav fab정도만 있고 리사이클러뷰로만 되있는거 아냐? 아이템 프리셋만 여러개 있고 그런게 아닐까

생각이 들었다

그렇다면 확실히 작업수행속도가 엄청나게 빠르고 레이아웃이 복잡해지지 않을테니 매우 효율적인것 같다는 생각도 들었음

0개의 댓글