[Android/Kotlin] Map 데이터를 꺼내서 리사이클러뷰에 넣는 방법

코코아의 개발일지·2023년 9월 27일
0

Android-Kotlin

목록 보기
11/31
post-thumbnail

✍🏻 요구사항 분석

{
  "status": 200,
  "result": {
    "2023-01-01": [
      {
        "ID": 7046,
        "GOAL_ID": 163,
        "TITLE": "오른쪽 상단 + 버튼으로 투두를 추가 할 수 있어요",
        "IS_FIN": 0,
        "IS_DELAY": 1,
        "ACTION_DATE": "2023-01-01"
      },
      {
        "ID": 7047,
        "GOAL_ID": 163,
        "TITLE": "오른쪽으로 밀면 내일로 미룰 수 있어요",
        "IS_FIN": 0,
        "IS_DELAY": 1,
        "ACTION_DATE": "2023-01-01"
      },
    ],
    "2023-01-02": [
      {
        "ID": 7097,
        "GOAL_ID": 163,
        "TITLE": "체크박스를 눌러 완료할 수 있어요",
        "IS_FIN": 1,
        "IS_DELAY": 0,
        "ACTION_DATE": "2023-01-02"
      },
      {
        "ID": 7104,
        "GOAL_ID": 163,
        "TITLE": "추가",
        "IS_FIN": 1,
        "IS_DELAY": 0,
        "ACTION_DATE": "2023-01-02"
      }
    ]
  },
  "message": "Success To Get Goal Todo Statistic",
  "success": true
}

이런 식으로 "2023-01-01", "2023-01-02" 식으로 날짜를 key로, 투두 정보를 body로 하는 데이터를 꺼내 리사이클러뷰에 보여줘야하는 상황이 생겼다. 심지어 날짜가 여러 개 있을 수 있고, 그 날짜마자 투두 정보가 있어 이중 리사이클러뷰를 써서 구현했었다. 또한, 목표마다 기간이 설정되어있는데, 이 날짜 데이터는 투두가 없는 날짜는 key로 보내주지 않았다.
Map 데이터를 아예 처음 다뤄봐서 어떻게 데이터를 꺼낼 수 있는지 많이 고민했고, 그걸 또 어떻게 리사이클러뷰에 넣을지 열심히 찾아봤다. 하지만 알아내지 못했다. 그래서 오늘은 다른 분께 여쭤봐서 답을 얻은 문제 해결 과정을 정리해보려 한다.


💻 코드 작성

핵심 개념

for(key: String in item.result.keys) {
  Key 값 (날짜) -> key
  안에 있는 아이템들 -> item.result[key]
}

-> 날짜를 key로 꺼내고, 그 안에 있는 아이템을 item.result[key]로 꺼내기

1️⃣ Data Class 작성

/** 목표 통계 중 Todo 리스트 조회 */
data class StatisTodoResponse(
    val result: Map<String, List<StatisticsDetail>>
) : BaseResponse()

data class StatisTodoResponsewithDate(
    val date: String,
    val detailList: List<StatisticsDetail>?
)
data class StatisticsDetail(
    @SerializedName("ID") val todoIdx: Int,
    @SerializedName("GOAL_ID") val goalIdx: Int,
    @SerializedName("TITLE") val title: String,
    @SerializedName("IS_FIN") val isFin: Int,
    @SerializedName("IS_DELAY") val isDelay: Int,
    @SerializedName("ACTION_DATE") val actionDate: String
)

String 형식으로 오는 날짜 데이터를 key로 꺼내줘야하는데, Map 형식의 데이터를 바로 리사이클러뷰에 넣을 수는 없으니 변환해주는 단계를 거쳐야하는데, 이 때 쓰이는 데이터 클래스가 바로 StatisTodoResponsewithDate()이다.

2️⃣ StatisticsDetailRVAdapter.kt (바깥쪽 어댑터)

class StatisticsDetailRVAdapter(var response: StatisTodoResponse, private var startDate: String, var endDate: String) : RecyclerView.Adapter<StatisticsDetailRVAdapter.ViewHolder>(){

    lateinit var items: ArrayList<StatisTodoResponsewithDate>

    @RequiresApi(Build.VERSION_CODES.O)
    fun build(i: StatisTodoResponse): StatisticsDetailRVAdapter {
        response = i
        items = arrayListOf()
        Log.d("StatisticsDetailRVA", "startDate: ${startDate}, endDate: ${endDate}")

        // 날짜, 시간을 가져오고 싶은 key 형태 선언
        val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale("ko", "KR"))

        val startLong = dateFormat.parse(startDate).time
        val endLong = dateFormat.parse(endDate).time
        val today = Calendar.getInstance().apply {
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }.time.time

        for (key: String in i.result.keys) {
            // 오늘의 한 일은 보여주지 않음
            if (key == dateFormat.format(today)) break
            // 오늘 한 일이 아니라면 데이터 넣어주기
            items.add(StatisTodoResponsewithDate(key, i.result[key]))
        }

        return this
    }

    class ViewHolder(val binding: ItemStatisticsDetailBinding, val context: Context) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(item: StatisTodoResponsewithDate) {
            with(binding)
            {
                val size = item.detailList?.size
                var isFinCnt = 0
                if (item.detailList != null) {
                    // 완료 개수
                    for (i in item.detailList) {
                        if (i.isFin == 1) {
                            isFinCnt += 1
                        }
                    }
                    // 미루기 개수
                    val isDelayCnt = size?.minus(isFinCnt)

                    // 날짜
                    itemStatisticsDetailDateTv.text = item.date.replace("-", ".")
                    // 완료 & 미루기 개수 표시
                    itemStatisticsDetailCompletePostponeTv.text = "완료: ${isFinCnt} / 미루기: ${isDelayCnt}"

                    // 목록 리사뷰
                    itemStatisticsDetailListRv.apply {
                        adapter = StatisticsDetailListRVAdapter().build(item.detailList!!) // 통계 차트 어댑터 연결
                        layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
                    }
                } else {
                    // 날짜
                    itemStatisticsDetailDateTv.text = item.date.replace("-", ".")
                    // 완료 & 미루기 개수 표시
                    itemStatisticsDetailCompletePostponeTv.text = "완료: 0 / 미루기: 0"
                }
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
        ViewHolder(
            ItemStatisticsDetailBinding.inflate(LayoutInflater.from(parent.context), parent, false),
            parent.context
        )

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(items[position])
    }

    override fun getItemCount(): Int = items.size 

}
  • 중요한 부분
fun build(i: StatisTodoResponse): StatisticsDetailRVAdapter {
        response = i
        items = arrayListOf()
        ...
        }

-> response에 데이터를 받아서 넣어줄 items를 arrayList로 설정함

for (key: String in i.result.keys) {
            // 오늘의 한 일은 보여주지 않음
            if (key == dateFormat.format(today)) break
            // 오늘 한 일이 아니라면 데이터 넣어주기
            items.add(StatisTodoResponsewithDate(key, i.result[key]))
        }

key를 꺼내서, key-result[key]의 데이터를 StatisTodoResponsewithDate에 넣어주는 과정!


그리고, 리사이클러뷰를 하나 더 호출해야해서

// 목록 리사뷰
itemStatisticsDetailListRv.apply {
	adapter = StatisticsDetailListRVAdapter().build(item.detailList!!) // 통계 차트 어댑터 연결
	layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}

이렇게 날짜 안의 투두 데이터를 넘겨주는 코드가 있다. 안 쪽에 있는 어댑터에서는 데이터를 어떻게 넣어줄지 살펴보자.

3️⃣ StatisticsDetailListRVAdapter.kt (안쪽 어댑터)

class StatisticsDetailListRVAdapter : RecyclerView.Adapter<StatisticsDetailListRVAdapter.ViewHolder>() {

    lateinit var items: List<StatisticsDetail>

    fun build(i: List<StatisticsDetail>): StatisticsDetailListRVAdapter {
        items = i
        return this
    }

    class ViewHolder(val binding: ItemStatisticsDetailListBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: StatisticsDetail) {

            binding.apply {
                itemStatisticsDetailListTitleTv.text = item.title

                if (item.isFin == 1) { // 완료
                    itemStatisticsDetailListDelayTv.visibility = View.GONE
                }
                else { // 미루기
                    itemStatisticsDetailListCompleteTv.visibility = View.GONE
                }
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
        ViewHolder(ItemStatisticsDetailListBinding.inflate(LayoutInflater.from(parent.context), parent, false))

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(items[position])
    }

    override fun getItemCount(): Int = items.size
}

앞선 바깥쪽 어댑터에서

// 목록 리사뷰
itemStatisticsDetailListRv.apply {
	adapter = StatisticsDetailListRVAdapter().build(item.detailList!!) // 통계 차트 어댑터 연결
	layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}

이렇게 안쪽 어댑터의 build로 데이터를 넘겨줬으니, 안쪽 어댑터에서는

lateinit var items: List<StatisticsDetail>

fun build(i: List<StatisticsDetail>): StatisticsDetailListRVAdapter {
        items = i
        return this
}

투두 안에 들어갈 데이터를 받아준다.
그 다음 투두 이름과 미루기/완료 여부를 받아주면 끝이다.


🤩 결과 화면

통계 상세보기 key 형태로 온 날짜를 잘 받았고, 그 안에 투두 데이터도 잘 보여주는 것을 볼 수 있다.

📚 참고 자료

도움을 주신 에릭께 감사드립니다.

profile
우당탕탕 성장하는 개발자

0개의 댓글