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

ConcatAdapter로는 순서를 섞어서 사용 못하나요?

0 추천
A_adapter = AAdapter(A_Items)
B_adapter = BAdapter(B_Items)
concatAdapter = ConcatAdapter(A_adapter, B_adapter)

ConcatAdapter를 사용할때, concatadapter에 set할때 이러한 형식으로 Adapter들을 셋한다고 가정하겟습니다.

그럼 아이템은 A 어댑터 이후 B 어댑터를 넣었으니 A아이템 나열이후 B아이템이 나열 될겁니다.

AAAABBBBB  이렇게요.

그런데 이걸 좀 유동적으로 순서를 섞을 순 없나요?

예를들면 ABBAAAABBBABABABABBB 이런식으로요..

즉 A아이템이 나오고 B 아이템이 나열이 되고 다시 A아이템이 나열되는 이러한 방식들이요..

이런걸 사용하려면 그냥 하나의 어댑터에 뷰타입을 검사하는 방법 밖에 없는지요

 

 

codeslave (3,940 포인트) 님이 2021년 12월 3일 질문
codeslave님이 2021년 12월 5일 수정
헤더아이템이 한개이던 여러개든 상관없습니다. 어차피 어댑터이고 리스트를 받게 되어있으니까, 필요한 상황에 따라 데이터를 만들어 주면 되는 거니까요? 말씀드렸으니, 어댑터를 하나만 사용하는 거랑 큰 차이가 없어요. 그리고 자료구조도 님이 취향이나 상황에 따라 저처럼 해도 되고, 다른 Github예제처럼 해도 됩니다. 빠르고 쉬운 걸 선택하면 됩니다.
감사합니다. 선생님이 다신 링크(다른분이 질문한것..)를 보았는데, 저랑 비슷한 것을 구현하시는것같던데 선생님이 답변하신걸보니까 Adapter한개에 멀티뷰타입을 해서 구분해서 하시는것 같더라구요, 그리고 TaskListItem에 레이아웃 아이디를 넣고 구분하구요.

여기서 정말 단순한 질문이 있는데요, 저는 ConcatAdapter로 한번 구현을 시도해볼까하는데요.. 그렇다면 저의경우에는 링크의 TaskItem처럼 굳이 sealed 클래스로 Header와 Child 아이템까지 둘 필요는 없죠? 그냥 각각의 data class만 존재해도 될까요?

그리고 이 둘을 Pair로 묶어주기만 해도되나요
어댑터를 하나만 쓰던 ConcatAdapter를 사용하던 구현이 좋고 편한 쪽으로 하시면 될 것 같아요. 당연히 어떤 어탭터를 사용하느냐에 따라 자료구조도 달라지겠죠.
그리고 저같은 경우는 어댑터 하나에 멀티뷰를 구현했기 때문에  sealed class를 사용한 겁니다. sealed class 의 장점 중의 하나가 when을 사용할 때 컴파일러가 해당 sealed class의 child class를 전부 체크하고 있는지 검사해 준다는 겁니다.
만약
when (item) {
    is ListItem.Header -> doSomething()
}

위처럼만 했다면 Kotlin compiler가  해당 sealed class 에는 ListItem.Item도 존재하는데 when 문장에서 사용하지 않기 때문에 경고를 날려줍니다. 버그를 방지하기에 더 좋죠.
당연히 그냥 data class 를 사용해도 됩니다. 정답은 없고 님의 선택에 달린 문제입니다.
그럼 제가 그룹당 한개의 헤더를 가져갈 예정인데요
val adapterItems: List<Pair<Header, List<ChildItem>>> = ....
for ((header, list) in adapterItems) { // 반복한번당 그룹한개.(헤더1 상세 x개)
     concatAdpater.addAdapter(HeaderAdapter(header))
     concatAdpater.addAdapter(ChildItemAdater(list))
}

또는 adapterList = arrayListOf<RecyclerView.Adapter>()
for ((header, list) in adapterItems) {
     adapterList.add(HeaderAdapter(header))
     adapterList.add(ChildItemAdater(list))
}

concatAdapter.adapter = adapterList

이 코드를 예시로하면 그룹당 Header는 하나만 존재하기때문에 또한 HeaderAdapter에 헤더아이템은 단 하나만 들어가는데,
이건 별 상관 없다는 말씀이시져?

프로젝트 하나만들어서 테스트코드를 진행해봣는데 제가 쓰고도 올바른건지 몰겠네요ㅋㅋ아이템이 한개만 존재하는 어댑터는 처음이라 이게 어댑터로써의 가치가 있는지 의문이었어서 자꾸 여쭈어보게되네요 죄송합니다ㅠ
네. 어차피 어댑터의 아이템 갯수가 한개이냐 아니냐는 중요하지 않아 보여요. ConcatAdapter 내부적으로 optimisation 을 하기 때문에 퍼포먼스도 별로 걱정하지 않아도 되구요.
저도 ConcatAdpater를 사용 중인데 아이테이 하나만 존재하는 어댑터들이 있어요. 이건 이슈가 아니라고 봐요. 어댑터가 어차피 0 - n개의 아이템을 받아드리도록 설계가 된거니까.

2개의 답변

0 추천

참고로 뷰모델에서 ConcatAdapter용으로 데이터를 변환하는 코드입니다. 님이 사용하는 데이터 구조에 따라 다르게 처리가 되긴 해야되겠죠.

val itemsByDao: List<TaskEntity> = dao.getAllTasks()

// List<Pair<String, List<TaskListItem.Item>>> 타입을 리턴할 경우
itemsByDao.groupBy { it.start_time }
     .entries
     .map { (header, list) ->
                header to list.map { task -> TaskListItem.Item(task) 
      }

// List<Pair<String, List<TaskEntity> 타입을 리턴할 경우
itemsByDao.groupBy { it.start_time }.toList()
   
     

이렇게 할 때, 한가지 확인해 보실 점은 변환한 결과의 순서가 정렬순서가 제대로 되었는지 여부입니다. 

 

spark (224,800 포인트) 님이 2021년 12월 4일 답변
0 추천

참고로 Adapter를 만들 때 기본 뼈대를 제공해주면 보일러 플레이드 코드없이 손쉽게 Adapter를 처리할 수 있을 것 같습니다.

abstract class GenericAdapter<T: ListItem>(initialItem: List<T> = emptyList(), diffCallback: DiffUtil.ItemCallback<T> = BasicItemCallback<T>()) :
    ListAdapter<T, GenericViewHolder<T>>(diffCallback) {

    init {
        this.submitList(initialItem)
    }

    override fun getItemViewType(position: Int): Int = getItem(position).layoutId

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<T> {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(viewType, parent, false)
        return createViewHolder(itemView)
    }

    abstract fun createViewHolder(itemView: View): GenericViewHolder<T>

    override fun onBindViewHolder(holder: GenericViewHolder<T>, position: Int) {
        holder.bind(getItem(position))
    }
}

interface ListItem {
    val layoutId: Int
}

이렇게 베이스 클래스를 만들고

class BasicItemCallback<T> : DiffUtil.ItemCallback<T>() {
    override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }
}

class TaskHeaderAdapter(item: TaskListItem.Header) :
    GenericAdapter<TaskListItem.Header>(initialItem = listOf(item)) {

    override fun createViewHolder(itemView: View): GenericViewHolder<TaskListItem.Header> {
        return TaskHeaderViewHolder(itemView)
    }
}

class TaskHeaderViewHolder(
    itemView: View
) : GenericViewHolder<TaskListItem.Header>(itemView) {

    private val binding by lazy { ItemTaskHeaderBinding.bind(itemView) }

    override fun bind(item: TaskListItem.Header) {
        binding.apply {
            startDateTxt.text = item.startTime
        }
    }
}

class TaskItemAdapter(tasks: List<TaskListItem.Item> = emptyList()) :
    GenericAdapter<TaskListItem.Item>(initialItem = tasks) {

    override fun createViewHolder(itemView: View): GenericViewHolder<TaskListItem.Item> {
        return TaskItemViewHolder(itemView)
    }
}

class TaskItemViewHolder(
    itemView: View
) : GenericViewHolder<TaskListItem.Item>(itemView) {

    private val binding by lazy { ItemTaskBinding.bind(itemView) }

    override fun bind(item: TaskListItem.Item) {
        val task = item.task
        binding.apply {
            val alarmResId = if (task.hasAlarm) R.drawable.ic_alarm_on else R.drawable.ic_alarm_off
            alarmImg.setImageResource(alarmResId)
            contentTxt.text = if (task.isDone) task.content.strikeThrough() else task.content
            endDateTxt.text =
                if (task.end_time.isNotBlank()) "~ ".plus(task.end_time.takeLast(5)) else ""
        }
    }
}

위와 같은 식으로 처리하면 코드량이 많이 줄어들 것 같습니다. 좀더 리팩토링하면 createViewHolder같은 부분도  더 간결하게 처리할 수 있을 듯합니다. 아무튼 보완하면 위의 코드보다 더 좋은 코드를 만들 수 있을 겁니다.

spark (224,800 포인트) 님이 2021년 12월 4일 답변
...