-
Sealed class로 RecyclerView Multi View Type 때려 부수기Android 2023. 2. 24. 02:30
안녕하세요! Mash-Up 10기 정현성 입니다.
이번에는 Room에 이어 Kotlin의 Sealed Class에 대해 포스팅하려고 합니다 :)
Sealed Class란?
Sealed Class는 상위 클래스를 상속받는 하위 클래스의 종류를 제한하는 특성이 있는 클래스입니다.
어떤 클래스를 상속받는 하위 클래스는 여러 파일에 존재할 수 있기 때문에 컴파일러는 얼마나 많은 하위 클래스들이 존재하는지 알지 못합니다.
하지만 Sealed Class는 동일 파일에 정의된 하위 클래스 외에는 존재하지 않는다는 것을 컴파일러에 알려줍니다.
예를 들어 Color 라는 상위 클래스를 만들고, 동일한 파일에 Color Class를 상속하는 Red Class, Black Class라는 클래스를 선언했다고 가정하면,
Sealed Class는 두 개의 클래스 외에 Color 클래스를 상속받는 다른 클래스는 없다는 것을 컴파일러에 말해주는 것과 같다고 합니다.
이렇게 하위 클래스의 종류를 제한하여 얻을 수 있는 장점 중 하나는 when 을 사용할 때 else 를 사용하지 않는 것입니다.
val color = "Red" val answer = when (color) { "Red" -> { "Red Color" } "Black" -> { "Black Color" } else -> { // else를 사용해야 error를 방지할 수 있다. "None Color" } }
sealed class Color { object Red: Color() object Black: Color() } val color : Color = Color.Red val answer = when (color) { is Color.Red -> "Red Color" is Color.Black -> "Black Color" // else를 사용하지 않아도 된다. }
이것은 코틀린에서 제공하는 Enum Class 로도 얻을 수 있는 장점입니다.
하지만 Sealed Class와Enum Class의 차이점은 하위 객체를 생성할 수 있는 개수에 있습니다. Enum Class는 하위 객체로 1개만 생성할 수 있지만,
Sealed Class는 1개 이상의 복수 객체를 생성할 수 있습니다. 예를 들어 Sealed Class는Color Class를 상속하여 Red Class, Black Class, Yellow Class …
1개 이상의 복수 객체를 생성할 수 있지만, Enum Class는Red Class 하나만 생성할 수 있다는 것입니다.
Sealed Class 정의 방법
다음과 같이 Sealed Class를 정의할 수 있습니다.
sealed class Color { object Red: Color() object Green: Color() object Blue: Color() }
클래스 앞에 sealed 키워드를 붙이면 이 클래스는 abstract 클래스가 됩니다. 그리고 하위 클래스가 이 클래스를 상속하도록 하면 됩니다.
val color: Color = Color.Red
이런 식으로 객체를 생성할 수 있습니다.
Sealed Class의 특징
- 클래스 이름 앞에 sealed 키워드를 붙여 정의한다.
- Sealed Class는 추상 클래스로, 객체로 생성할 수 없다.
- Sealed Class의 생성자는 private 이다.
- Sealed Class와 그 하위 클래스는 동일한 파일에 정의되어야 한다.
RecyclerView Multiple View Type에 Sealed Class 적용해 보기!
Sealed Class 만들기
sealed class UIModel { data class FeedyModel( val title: String, val description: String, val businessName: String ): UIModel() data class PromotionModel( val title: String, val description: String, val image: String ): UIModel() data class RatingCardModel( val title: String, val description: String, val link: String, val button_title: String ): UIModel() }
RecyclerView Adapter에서 사용하려는 모든 data class를 만들고 sealed class를 상속해 줍니다. 이렇게 Sealed Class 구현을 마쳤습니다.
class FeedAdapter(context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var arrayList: ArrayList<UIModel> = ArrayList() fun submitData(list: ArrayList<UIModel>) { arrayList.clear() arrayList.addAll(list) } override fun getItemCount(): Int = arrayList.size override fun getItemViewType(position: Int): Int { return super.getItemViewType(position) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }
이제 RecyclerView adapter를 구현해 줄 차례입니다. 위 코드는 기본적인 RecyclerView adapter를 구현한 코드입니다.
override fun getItemViewType(position: Int) = when (arrayList[position]) { is UIModel.FeedyModel -> R.layout.adapter_feed is UIModel.PromotionModel -> R.layout.adapter_promotion is UIModel.RatingCardModel -> R.layout.adapter_rating }
getItemViewType()으로 position에 따라 적절한 View Type을 반환해 주는 코드를 작성합니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val v = layoutInflater.inflate(viewType, parent, false) return when (viewType) { R.layout.adapter_feed -> FeedViewHolder(v) R.layout.adapter_promotion -> PromotionalCardViweHolder(v) else -> RatingCardViweHolder(v) } }
이제 onCreateViewHolder에 viewType에 따라 각각의 ViewHolder를 생성해 줍니다.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = arrayList[position] when (holder) { is FeedViewHolder -> holder.onBindView(item as UIModel.FeedyModel) is PromotionalCardViweHolder -> holder.onBindView(item as UIModel.PromotionModel is RatingCardViweHolder -> holder.onBindView(item as UIModel.RatingCardModel) } }
마지막으로 adapter가 UI에 data를 반영할 수 있도록 현재의 아이템 데이터와 함께 view holder를 업데이트해 주는 것입니다.
class FeedAdapter(context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var arrayList : ArrayList<UIModel> = ArrayList() fun submitData(list : ArrayList<UIModel>){ arrayList.clear() arrayList.addAll(list) } override fun getItemCount(): Int = arrayList.size override fun getItemViewType(position: Int) = when (arrayList[position]) { is UIModel.FeedyModel -> R.layout.adapter_feed is UIModel.PromotionModel -> R.layout.adapter_promotion is UIModel.RatingCardModel -> R.layout.adapter_rating } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val v = layoutInflater.inflate(viewType, parent, false) return when (viewType) { R.layout.adapter_feed -> FeedViewHolder(v) R.layout.adapter_promotion -> PromotionalCardViweHolder(v) else -> RatingCardViweHolder(v) } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = arrayList[position] when (holder) { is FeedViewHolder -> holder.onBindView(item as UIModel.FeedyModel) is PromotionalCardViweHolder -> holder.onBindView(item as UIModel.PromotionModel is RatingCardViweHolder -> holder.onBindView(item as UIModel.RatingCardModel) } } }
최종적으로 작성된 RecyclerView adapter 코드입니다.
RecyclerView Multiple View Type 구현에 Sealed Class를 활용하면, 코드가 좀 더 유연해지고 확장성이 높아진다고 합니다.
이번 한 달 프로젝트에 적용해 보겠습니다 :)
'Android' 카테고리의 다른 글
Bubbles (1) 2023.02.24 RecyclerView ListAdapter (0) 2023.02.24 Lock Screen (0) 2023.02.24 Jetpack Navigation (0) 2023.02.24 View Binding in Fragment (0) 2023.02.24