Skip to content
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

feat: 모험 기록 상세 페이지 뷰 구현 #494

Merged

Conversation

krrong
Copy link
Collaborator

@krrong krrong commented Oct 17, 2023

📌 관련 이슈

🛠️ 작업 내용

  • 모험 기록 상세 페이지 뷰 구현
  • 뷰페이저2 + 탭 레이아웃을 사용하여 읽은 쪽지, 등록한 쪽지를 볼 수 있도록 구현
  • 읽은 쪽지 or 등록한 쪽지가 없는 경우 쪽지가 없다는 내용이 뜨도록 구현

🎯 리뷰 포인트

ViewPager2

  • 뷰페이저의 각 페이지는 리사이클러뷰로만 구성되어 있습니다. 그래서 RecyclerViewAdapter를 사용해서 ViewPager의 Adapter를 만들어줄 수 있었고 해당 방식을 사용하여 구현했습니다.

UiState

  • 새로 생긴 화면에 UiState를 적용했습니다. 이는 여러 개의 상태 값을 따로 따로 관리하는 것이 아니라 캡슐화된 클래스 하나로 관리하기 위한 방법과, 뷰에서 보았을 때 적절한 상태에 따라 분기처리하여 다른 작업을 하기 용이하도록 도와줄 수 있습니다.
  • 자주 변하는 값을 UiState에 저장하는 것은 Copy의 비용에 따라 다를 수 있습니다.

Flow

Flow를 적용하게 된 이유는 다음과 같습니다.

  • 우리의 API는 QueryString의 Type을 다르게 하여 읽은 쪽지와 등록한 쪽지를 읽어오는 구조입니다. 즉, 두 번의 요청이 필요하다는 뜻이죠.
  • 뷰페이저를 띄우기 위해서는 읽은 쪽지와 등록한 쪽지가 모두 필요합니다. 그래서 두 요청이 모두 성공한 이후에 뷰를 그려야 합니다. Flow에는 combine이라는 기능이 있고, 이는 a와 b Flow의 값이 올 때까지 기다렸다가 합쳐서 방출하는 기능을 합니다.
  • viewModelScope 안에서 async(), await()메서드를 사용하여 구현할 수도 있지만 Flow에서 이미 제공하는 기능을 사용하는 것이 낫지 않을까 생각했습니다. (부족한 시간에 괜히 한 것 같기도 합니다. ㅋㅋ)

⏳ 작업 시간

추정 시간: 1h 30m
실제 시간: 8h

수치상으로 보니 차이가 많이 나네요. 뷰를 그리다가 욕심이 나서 뷰페이저를 추가했고, 쪽지 하나의 뷰도 조금이지만 꾸며봤습니다.
Flow를 처음써봐서 학습과 구현 방식에 대해 많이 고민하는 시간을 가졌네요. 추가로 Throwable을 LiveData가 아닌 방식으로 어떻게 구현할 수 있을까에 대해 고민도 해보았습니다.

@krrong krrong requested a review from briandr97 October 17, 2023 08:38
@krrong krrong removed the request for review from briandr97 October 17, 2023 08:53
@krrong krrong removed the 빅스💻 label Oct 17, 2023
Copy link
Collaborator

@hyunji1203 hyunji1203 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크롱!🦖 고생많았어요!
잠못자고 한 이유가 있었네요ㅋㅋㅋ아니 uiState에 Flow까지 👍
저도 크롱 리뷰하면서 플로우 공부를 같이 좀 했습니다ㅋㅋㅋ
특별히 큰 수정 요구사항은 없어보여 몇가지 질문 및 코멘트 남겼으니 확인해주시면 됩니다!

writeLetters = getOpenLetterUiModels(writeLetters),
adventureResult = adventureResult,
)
}.collectLatest { _uiState.value = it }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collectLatest 가 어떤 일을 하는 아이인가요? 최종적으로 값이 모여졌을 때 value에 넣어주도록 처리해주는 아이인가요??!?!!? 진짜 모름

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collect를 통해 emit한 값을 수집하여 사용할 수 있습니다.
collectLatest 함수는 새로운 값이 방출될 때마다 현재 진행 중인 작업을 취소하고, 가장 최신의 값을 사용하여 새로운 작업을 시작합니다.
다음의 코드를 실행시켜보면 조금 더 쉽게 와닿을 수도 있을 것 같아요!

fun main() = runBlocking {
    val flow = flow {
        emit(1)
        delay(1000)
        emit(2)
        delay(1000)
        emit(3)
    }

    flow.collectLatest { value ->
        println("Received: $value")
        delay(3000)
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호... 예제 코드까지 주시다니.. 👍
해당 코드 실행 시켜봤는데 이해가 좀 가기 시작했어요! flow 관련해서 좀더 공부해보고 싶어지네요ㅎㅎㅎㅎ

runCatching {
letterRepository.fetchLetterLogs(gameId, LogType.READ)
}.onSuccess {
readLettersFlow.emit(it)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호..... init 에서 컴바인을 해주면 여기선 success가 될 때 emit만 해주면 되는군요..?
크롱의 flow 강의 기다립니다🤩

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init 블록에서 readLettersFlow, writeLettersFlow, adventureFlowcombine하고
collectLatest를 통해 가장 최신의 값을 사용하여 AdventureDeetailuiState.Success를 반환합니다.

emit을 해야 collect가 가능합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 ~~~~~~~~~ combine된 애들이 emit을 통해 값이 불러와지고 이 아이들이 다 들어오게 되면 collectLatest를 통해 받아오는거군요
오호 대박 이해완!

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

fun LifecycleOwner.repeatOnStarted(block: suspend CoroutineScope.() -> Unit) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수는 어떤 일을 하나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LiveData와는 다르게 Flow는 안드로이드의 라이프사이클을 알지 못합니다. 그래서 onStart()에서 collect를 시작하고, onStop()에서 cancel해줘야 하는데요.
Lifecycle의 확장함수인 Lifecycle.repeatOnLifecycle은 파라미터로 넣어주는 State에서만 collect를 합니다. 그리고 State 이외의 상태로 갔을 때 collect을 멈추고 다시 State로 돌아가면 collect을 재진행합니다.
따라서 State에 Lifecycle.State.STARTED를 넣어주면 onStart() 에서만 collect을 할 수 있도록 지정해줄 수 있고, 이를 편하게 사용하기 위한 확장함수를 만든 것입니다. (안만들면 인덴트가 3개는 가뿐히 넘어가더라구요..?)

여기를 참고하면 도움이 될 수도 있겠습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호.... 크롱 덕분에 Flow에 대해 알아가네요! 신기해요! 많이 배워갑니다!!

}
}

private fun showReRequestSnackbar() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2]

Suggested change
private fun showReRequestSnackbar() {
private fun showRequestSnackbar() {

오타가 있어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오타...는 아닙니다..🦖
재요청 == Re(재)Request(요청)
의 느낌으로 작성한건데... showRequestSnackbar로 변경할까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋㅋ오타가 아니군요!? 앗차차~~~
showRetrySnackbar는 어떤가요?ㅋㅋㅋㅋ구리면 바로showReRequestSnackbar로 유지합시다!ㅋㅋㅋ
크롱 말 듣고 다시 보니 showReRequestSnackbar 네이밍도 메서드가 머선일을 하는지 잘 보이는 것 같고 좋네요👍

Comment on lines 11 to 12
fun getDefault(): OpenLetterUiModel {
return OpenLetterUiModel("", "", DEFAULT_MESSAGE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3]
Default 객체를 반환해주는 함수를 생성해 매번 생성해주기 보단 아예 디폴트 객체를 하나 만들어 놓는 건 어떤가요?

Suggested change
fun getDefault(): OpenLetterUiModel {
return OpenLetterUiModel("", "", DEFAULT_MESSAGE)
val DEFAULT_OPEN_LETTER = OpenLetterUiModel("", "", DEFAULT_MESSAGE)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다!

…23-naaga into feat/#489_모험_기록_상세_페이지_뷰_구현

# Conflicts:
#	android/app/src/main/java/com/now/naaga/data/throwable/DataThrowable.kt
Copy link
Collaborator

@hyunji1203 hyunji1203 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생많았어용~~~~~
리뷰 반영한 부분도 확인했고 나머지 로직들도 이해 완료 했습니다!
더 이상 수정할 부분 없는 것 같아 이만 approve하고 merge 하도록 하겠습니다!
👍🦖

@hyunji1203 hyunji1203 merged commit 0161ce9 into dev_android Oct 17, 2023
@hyunji1203 hyunji1203 deleted the feat/#489_모험_기록_상세_페이지_뷰_구현 branch October 17, 2023 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants