{
"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]
로 꺼내기
/** 목표 통계 중 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()
이다.
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)
}
이렇게 날짜 안의 투두 데이터를 넘겨주는 코드가 있다. 안 쪽에 있는 어댑터에서는 데이터를 어떻게 넣어줄지 살펴보자.
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
}
투두 안에 들어갈 데이터를 받아준다.
그 다음 투두 이름과 미루기/완료 여부를 받아주면 끝이다.
도움을 주신 에릭께 감사드립니다.