-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Team-02_Android] [Linus_Funny] 4번째 PR 요청 _ API 통신 + 드래그 앤 드롭 + 활동 기록 #212
base: team-02
Are you sure you want to change the base?
[Team-02_Android] [Linus_Funny] 4번째 PR 요청 _ API 통신 + 드래그 앤 드롭 + 활동 기록 #212
Conversation
[funny] 피드백 반영, retrofit 적용 중
[funny] 피드백 반영과 레트로핏 적용중
[funny] network 추가 기능 구현
[funny, linus] 글 추가기능 Api 연동
delete와 drag, drop api 연결
[funny] 드래그앤드롭, 삭제 api 연결
롱클릭 팝업 메뉴 구현
[퍼니] 롱 클릭 팝업 메뉴 구현
- swipe 오류 수정 - 레이아웃 일부 보완
[퍼니 & 라니어스] 활동 기록 구현
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다~
레이어를 여러 단계로 나누는 시도를 해주셨는데요, reposioty, dataSource 등을 잘 생각하고 구현을 해주셨으나 일부 구현에서 서버측의 변경 사항이 뷰모델 까지 올라올 가능성이 있습니다.
레이어 분리의 목적은 각 계층의 변경이 독립적으로 이루어 지는것에 있는데요. 아래 상황에서 관련 코드만 변경될 수 있도록 짜보면 이해가 좀 될수 있어보입니다 .
예시
- 어느날 특정한 이유로 retrofit 을 못쓰고 다른 라이브러리를 써야한다.
- 서버에서 주는 데이터 모델이 camel case 에서 snake_case 로 바뀌었다.
- 서버 데이터를 사용하는 쪽에서는 enum 을 활용하고 싶다.
리뷰 확인만 하시고 바로 머지가셔도 됩니다~
override suspend fun addCard(newCard: NewCard): Response<Card> { | ||
return apiClient.addCard(newCard) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 메소드에서 Card 를 리턴해야 하는 상황이 어떻게 될까요?
아마 이 카드를 추가 할 때는 이미 new card 라는 데이터가 있어서 보기에는 같은게 아닐까? 생각이 들었습니다.
suspend fun getCards(): Data? { | ||
val cards = cardDataSource.getCards() | ||
return cards.body()?.data | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getCards() 메소드를 사용하는 사람은 리턴값으로 List 를 기대할 확률이 높습니다. 또한, Data 는 서버쪽에서 주체를 가지는 형태로 보이는데요, Repository 등으로 레이어를 나누는 것은 서버는 어떻게 구현되는지 어떤 모양의 데이터를 사용하는지 모르게 하는것에도 목적이 있습니다.
포인트가 약간 감이 안 올수도 있는데, 두 리턴 타입을 놓고 장단점을 생각해보는 연습을 해보시면 좋겠습니다!
suspend fun addCard(subject: String, content: String, status: String): Card? { | ||
return cardDataSource.addCard(NewCard(subject, content, status)).body() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 정말 리턴 타입이 꼭 필요한 상황인지 확인 부탁드립니다!
val cards = cardRepository.getCards() | ||
cards?.todo?.cards?.sortedByDescending { it.order }?.let { setTodoList(it) } | ||
cards?.ongoing?.cards?.sortedByDescending { it.order }?.let { setOngoingList(it) } | ||
cards?.completed?.cards?.sortedByDescending { it.order }?.let { setCompletedList(it) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
너무 많은 safe call 은 코드의 동작이 모호하거나 나중에 디버깅이 매우 어려울 수 있습니다. (관련된 변수중 하나라도 null 일 경우 아무 예외/메시지 없이 지나감)
이를 줄이는 방법에는 여러가지가 있습니다. 검사하는 부분을 앞쪽에 모아놓거나, 변수를 새로 도입하거나 등의 방식으로 가능하면 ? 를 줄여보면 좋겠습니다!
override fun addCard(newCard: NewCard) { | ||
viewModelScope.launch { | ||
val card = cardRepository.addCard(newCard.subject, newCard.content, newCard.status) | ||
when (card?.status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
status 가 일반 문자열 대신 enum 이었으면 더 명확할수 있을 코드로 보입니다.
이런 타입의 경우 쓰는 쪽에서 매번 어떤 문자열이 올 수 있는지 다른 사람의 코드를 찾아다니면서 확인해야 하거든요.
위 목적을 달성하려면 네트워크용 클래스/앱 전용 클래스의 분리가 선행되어야 합니다. 앞으로 다른 과제도 진행해보시면서 고민해보시면 좋겠네요~
val cardId = draggedCard.cardId?.let { it } | ||
?: throw IllegalArgumentException("cardId is null!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
같은 역할의 코드를 requireNotNull 같은 함수를 사용하면 가독성 좋게 만들어 볼수도 있습니다.
실제로 줄어드는 코드는 얼마 안되지만, 아래쪽에 비슷한 코드들이 반복되므로 더 명확하게 보이는 효과가 있습니다.
val ongoingAdapter = CardAdapter(cardViewModel) | ||
binding.rvOngoing.adapter = ongoingAdapter | ||
val completedAdapter = CardAdapter(cardViewModel) | ||
binding.rvCompleted.adapter = completedAdapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adapter 에서는 되도록이면 뷰모델 의존성이 없는게 좋을때가 많습니다.
지금과 같은 구현은 뷰모델을 여러 단계의 레이어에서 건드리게 되므로, 무언가 변경이 매우 어려워질 확률이 큽니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
밑에서 확인해보니 이미 개선해주셨군요 :)
class CardAdapter( | ||
private val cardActionHandler: CardActionHandler | ||
) : ListAdapter<Card, CardAdapter.CardViewHolder>(DiffUtil) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아, 이런식으로 뷰모델 의존을 한번 끊으셨군요!
이렇게 되면 뷰모델 의존성이 사라지므로 adapter 을 약간 더 자유롭게 사용 할 수 있습니다!
질문 사항 답변
일단 저도 실제 서비스에서 드래그드롭을 구현한적이 없어서 애매하지만, 약간의 의견을 첨부드릴게요. 물론 그래도 필요하다면, 아예 리사이클러뷰를 빼고 나만의 드래그 드롭이 가능한 커스텀 뷰를 새로 구현 할 듯 하네요. 커스텀 뷰를 만들듯이 커스텀 뷰그룹에 대한 학습을 하고 만들면 됩니다. 혹은 Canvas 를 사용하여 작은 부분부터 그리는 방법도 있습니다! |
작업 현황
문제점
(저희 코드에서는 스크롤하여 viewHolder에 bind할 때 스와이프를 풀도록 함)
결과
질문
소감
Linus : 친절하게 알려주셔서 감사합니다. 확실히 저희가 생각하지 못한 부분이 있어 (프래그먼트에는 생성자 x 등) 많은 점을 배웠습니다. 저희가 기능을 구현하는데 급급하여 원활하게 소통하지 못한 점이 아쉽지만 혹시나 다음에 다시 리뷰를 받게 된다면 더 열심히 하도록 하겠습니다. 감사합니다 :D
Funny : 좋은 피드백 감사합니다. 모든 피드백을 반영해보고 싶었지만 저희의 실력 부족으로 모두 반영하지 못해 많이 아쉬웠습니다. 다음에는 네이버에서 직접 피드백을 받을 수 있도록 하겠습니다 ㅎㅎ