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

Epoxy 라이브러리 사용해보신분 계신가요?

0 추천

https://imgur.com/a/udnEfM8

 

사진이 커서 링크로 대체해서 올립니다. (사진 2개) - 기능만 구현하느라 UI는 덜 다듬었습니다ㅠ

링크내의 사진과같은 것을 만들고 있는데요,

우여곡절끝에 해당 기능을 위해서

sealed 클래스내의 모델(아이템) 2개를 어댑터하나에 멀티타입뷰를 사용해서 

리스트를 나열하는 식으로 구현을 했습니다.

문제는 멀티뷰타입으로 쭉 리스트를 쭉 나열하는 형태라 그런지

세트 추가버튼을 눌렀을때, 각 Routine 아이템에 맞게 추가가되는것이아니라

제일 마지막에 무조건 Detail 아이템이 추가된다는 것입니다.

이부분은 아직 로직을 생각을 안해봤는데.. 어떻게 버튼을 눌렀을때 해당 Routine 포지션을 찾고 추가를 해야할지 막막하긴하네요 또.

 

아무튼 이리저리 보다가 Epoxy 라이브러리라고 복잡한 RecyclerView를 사용할때

좋은 라이브러리라고해서 Git 링크가 있길래 들어가봤는데..

그..별?도 7.8k 정도로 많고 (사실 진짜 많은게 어느정도인지 모르겠습니다..)

구글링해도 코드구현 관련 포스팅해서 나오길래 이걸로 교체를 해볼까하고 고민중인데

생각해보니 저는 아이템변화가 동적으로 일어나고 또 위에서도 말햇듯이

아이템 추가가 Routine 아이템에 맞는 위치에 Detail 아이템이 추가되어야하는데

결국 Epoxy 라이브러리를 쓰나 기존 방식으로하나 

알맞은 위치에 넣는 로직을 구현해서 넣는건 똑같은거 아닌가해서

이게 맞는지 궁금해서 여쭈어봅니다..

아니면 Epoxy가 이러한 로직을 편하게 해주는 기능같은게 있나요?

 

------------------

 

sealed class RoutineItem() {
    data class RoutineModel(
        val workout: String, // 운동 종목
        val unit: String, // 무게 단위 (kg or lbs)
        var routineDetail: List<DetailModel> = listOf()
    ) : RoutineItem()

    data class DetailModel(
        val set: String, // 세트
        val reps: String = "1",
        val weight: String
    ) : RoutineItem()
}

 

Viewmodel

class WriteRoutineViewModel : ViewModel() {
    private var _items: MutableLiveData<List<RoutineItem>> = MutableLiveData(listOf())
    private val routines = mutableListOf<RoutineItem>()
    var set: Int = 1

    val items: LiveData<List<RoutineItem>> = _items 

    fun addRoutine(workout: String) {
        routines.add(RoutineItem.RoutineModel(workout, "TEST"))
        routines.add(RoutineItem.DetailModel("1","3","3"))
        _items.postValue(routines)
    }

    fun addDetail(pos: Int) {

        routines.add(RoutineItem.DetailModel("1","3","3"))
        _items.postValue(routines)
    }

    fun deleteDetail(pos: Int) {

    }
}

 

codeslave (3,940 포인트) 님이 2021년 10월 2일 질문
codeslave님이 2021년 10월 3일 수정

1개의 답변

0 추천

현재 프로젝트 초창기에 많이 사용했다가 memory leak이 많아서 전부 제거했었습니다. 1년이 훨씬 넘었으니까, 그 부분은 아무래도 개선이 많이 되었을 것 같네요. Expoxy가 쓸데없는 코드를 없애 주어서 사용하긴 좀 편리할 겁니다. 여전 RecyclerView기반위에서 돌아가는 거지만요 사용하고 말고는 각자의 요구사항에 따라 달라지겠죠. 

제가 볼 때, 님의 경우 제일 중요한 부분은 리스트에 사용할 데이터 구조예요. 이 부분을 잘 가져가셔야 Epoxy를 쓰던, 그냥 RecyclerView를 사용하던 문제가 덜 복잡할 것 같아요. 

그리고 겪고 계신 문제는 parent와 child 아이템 간에 key 값을 가지고 해결하는 걸 생각해 보시는게 어떨까 싶어요. RecyclerView에 보여지는 아이템을 님이 실제 처리하는 도메인 데이터와 다를 가능성이 커요. 따라서 화면 상에 보여지는 데이터는 도메인데이터를 적절하게 다르게 사용하시는게 정신건강에 좋아요.

화면 상에 보여지는 데이터 구조는

selead class ListItem {
    val id: String,
    val title: String

   class Parent(
         id: String,
         title: String
    ): ListItem(id, title)

   class Child(
         id: String,
         title: String
    ): ListItem(id, title)
}

Or

data class ListItem(
   val id: String,
   val title: String,
   val isHeader: Boolean
)

위의 둘 중의 하나에 해당하는 구조이면 될 것 같구요. RecyclerView Adapter에서 추가버튼을 눌렀을 때는 id 값을 가져와서 님이 비지니스로직에 사용하는 데이터의 id와 비교해서 같은 id를 가진 곳에 새 아이템을 추가를 하면 될 것 같아요.

// 비지니스 로직에 사용하는 데이터가 아래처럼 있다고 하면
data class ParentVo(
    val id : String,
    val name: String,
    val children: ArrayList<ChildVo> = arrayListOf()
)

data class ChildVo(
   val id: String,
   val name: String
)

private val Items = arrayListOf<ParentVo>()


// Aapter에서 추가 버튼 클릭 시
fun addButtonClicked(parentItem: ListItem.Parent) {
     val parentToAdd: ParentVo = items.find { it.id == parentItem.id }!!
     parentToAdd.children.add(ChildVo(...))
}

// 화면에 보여질 데이터로 변환
fun getListitems(): List<ListItem> {
      val listItems = arrayListOf<ListItem>()
      for (parentVo in items) {
            listItems.add(ListItem.Parent(parentVo.id, parentId.name)) 

            val childListItems = parentVo.map { childVo ->
                ListItem.Child(chidVo.id, childVo.name)
             }

           listItems.addAll(childListItems)
      }
      return listItems
}

 

핵심은, 비지니스 로직에 사용할 데이터 구조와 화면에 사용할 데이터 구조를 분리하시고 서로 연결이 되는 값을 공유하게 하는 겁니다.

spark (224,800 포인트) 님이 2021년 10월 2일 답변
spark님이 2021년 10월 2일 수정
감사합니다. 결국은 RecyclerView를 쓰던 Epoxy를 쓰던 중요한건 아니고 저한테 지금 중요한건 로직 구현이라는 말씀이시죠? Epoxy가 제가 원하는것을 가능케해주는것은 아니고, 데이터구조와 추가삭제로직을 어떻게 짜느냐가 중요하다는 말씀이죠?

그리고 저도 key(id)를 넣는것을 생각을 해봤었는데, 선생님은 지금 ListItem, parent와 child 셋다 id를 넣으셨잖아요?

sealed 클래스를 사용하면 대충 본문의 추가한 코드 형태가 그림 이미지처럼 될것이죠.

링크의 이미지에서처럼 보이는 UI에서는 RoutineModel과 DetailModel이 모두 각각처럼 보이지만실상은
하나의 RoutineItem안에 RoutineModel 하나와 DetailModel이 여러개 있는 형태일거란 말이죠. 즉 아주 크게보면 RoutineItem이 리사이클러뷰에 나열되어있다고 볼 수도 있지 않을까요?
다만, 안에서 세부적으로 뷰타입을 구분해서 나열했을뿐이지.


그래서 제 생각은 그 RoutineModel과 DetailModel의 아이디 말고
RoutineItem의 키(아이디) 값만 넣고 사용해서 DetailModel을 추가하는건 어떨까 생각 해봤는데 어떤가요?
해당 RoutineItem만 안다면 DetailModel을 사이즈를 계산한뒤에 제일 마지막에만 추가해주면 되니까요..?

그렇다면 본문 ViewModel의 addRoutine()가 진행되면 RoutineItem을 만들고 _item에 넣어준후에 addDetail에서는 클릭한 위치에 맞는 RoutineItem을 찾아서 DetailModel을 추가하고 추가되어 바뀐 RoutineItem 리스트를 Adapter로 전달하여 다시 그리는 방식이 될 것 같은데..(본문 ViewModel 코드는 일단 엉망입니다ㅜ)

저는 이렇게 생각해봤는데 어떤가요? 물론 아직 구현은 안해봤고 머릿속으로 생각만 해봤습니다..의견좀 여쭈어봅니다..

제가 하고싶은 말이 제대로 전달됐으려나 잘모르겠습니다 감사합니다
position에 따라 아이템을 구분할 수도 있지만, 이건 비지니스 레이어에서 사용되는 아이템과 뷰 레이어에 사용되는 아이템의 순서가 같을 것이라는 전제하에서만 가능합니다.
한가지 예를 들면 Filter를 적용하는 경우를 생각해보죠. 비지니스 레이어든, 뷰레이어든 데이터를 필터를 해서 가져오거나 보여줘야 하는 경우라면, 실제 화면에 보이는 위치의 데이터가 비지니스 레이어가 일치하지 않을 수가 있습니다. 그리고 비지니스 레이어가 List를 사용하지 않고 Set이나 Map, Tree 같은 걸 사용한다고 하면, position 에 따라 아이템을 구분하는 걸 잠정적인 문제가 있을 수 있습니다. 따라서 key 를 이용하는 것이 좀 더 정확한 방법이 될 것 같습니다. 결론적으로는 님의 요구사항에 따라 가장 적합한 방법으로 결정해야 하겠죠.
그렇군요.. 비지니스 레이어, 뷰래이어.. 아직 저는 뭐가 뭔지 몰라 구분하기는 어렵지만 포지션보다는 키사용을 하는쪽으로 가봐야겠네요.

그리고 선생님 코드에서 키 값을 strinf으로 설정하셨던데, 결국는 키값도 제가 설정해야하는
것이잖아요?
그런데 단순 strinf값이면 너무 추상적인 것같고, 다른 아이팀과 겹치지않는 고유한값이어야할것같은데 이러한 부분은 어떻게 설정할 수 있나요?
그건 님이 사용하는 데이터가 어떤 것인지에 따라 달라질 수 있겠죠. 회원ID처럼 멸료한 키값이 존재한다면 그걸 사용하면 될 것같고, 없다면 자동증가값 같은 걸 사용할 수도 있겠죠. SQLite같은 경우는 자동증가필드가 키필드의 기본적인 데이터 타입입니다. String같은 경우는 UUID를 사용할 수도 있을 것 같구요. 어쨋든, 실제 사용하는 데이터와 밀접하게 연관이 되어 있으므로, 상황에 따라 달라질 수 있습니다.
...