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

[WEEK7] 7차 필수과제 구현 #14

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open

[WEEK7] 7차 필수과제 구현 #14

wants to merge 4 commits into from

Conversation

HAJIEUN02
Copy link
Contributor

@HAJIEUN02 HAJIEUN02 commented Dec 11, 2024

Related issue 🛠

Work Description ✏️

  • MVI 패턴 적용 및 수정

Screenshot 📸

XRecorder_05112024_115538.mp4

4차/6차 과제와 같은 영상입니다!

Uncompleted Tasks 😅

N/A

To Reviewers 📢

안드로이드 초기 데이터 로드 해당 아티클 및 합동세미나를 거쳐 오면서 초기 데이터 로드에 LaunchedEffect(Unit)를 사용하던 것을 수정했는데요! MyViewModel을 보시면 다음과 같이 flow를 이용해서 emit하고, 이를 관찰하던 MyRoute에서 해당 state가 Success인 경우 MyScreen을 호출하는 것으로 구현을 해두었습니다.

이번에 Home 뷰도 My 뷰처럼 flow를 이용해서 수정하고 싶었는데 어떻게 수정해야 할지 감이 잘 안 오더라구요ㅜ.ㅜ 일단 LaunchedEffect(homeUiState)를 이용해서 Idle인 경우 뷰모델의 setHomeImgList(실제 앱이라면 서버통신 하는 함수)를 호출하는 것으로 구현을 해두었습니다. flow를 이용해 좀더 깔끔하고 좋은 방향으로 개선할 방법이 있을까요? 조언해주시면 감사하겠습니다!

@HiltViewModel
class MyViewModel @Inject constructor(
    private val patchUserHobbyUseCase: PatchUserHobbyUseCase
) : ViewModel() {
    val myUiState: StateFlow<MyUiState> =
        flow<MyUiState> {
            runCatching {
                patchUserHobbyUseCase()
            }.onSuccess { data ->
                emit(MyUiState.Success(data.hobby))
            }.onFailure { throwable ->
                emit(MyUiState.Error(throwable.message))
            }
        }.catch { throwable ->
            emit(MyUiState.Error(throwable.message))
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = MyUiState.Loading
        )
}

@Composable
fun MyRoute(
    paddingValues: PaddingValues,
    myViewModel: MyViewModel = hiltViewModel()
) {
    val uiState by myViewModel.myUiState.collectAsStateWithLifecycle()

    when(val myUiState: MyUiState = uiState) {
        is MyUiState.Success -> {
            MyScreen(
                modifier = Modifier.padding(paddingValues),
                userHobby = myUiState.hobby
            )
        }
        else -> Unit
    }
}

@HAJIEUN02 HAJIEUN02 self-assigned this Dec 11, 2024
@HAJIEUN02 HAJIEUN02 linked an issue Dec 11, 2024 that may be closed by this pull request
1 task
@HAJIEUN02 HAJIEUN02 requested review from hyeeum, kamja0510, 1971123-seongmin and jihyunniiii and removed request for hyeeum and kamja0510 December 13, 2024 06:58
Copy link

@1971123-seongmin 1971123-seongmin left a comment

Choose a reason for hiding this comment

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

항상 보고 공부하고 배울점이 정말 많은 것 같아요 😚😚
과제하느라 고생하셨습니다.

editorRecommendedImgList: List<String>,
todayTopRankingImgList: List<String>,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
pagerState: PagerState = rememberPagerState(pageCount = { 0 }),

Choose a reason for hiding this comment

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

HomeScreen에서 rememberPagerState는 어떻게 사용되는건가요??


LaunchedEffect(Unit) {
homeViewModel.setHomeImgList()
}

LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->

Choose a reason for hiding this comment

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

snapshotFlow는 혹시 사용하신 이유가 있나요??

}.onFailure {
Timber.d("[로그인] 실패 -> $it")
setEffect(LoginContract.LoginEffect.ShowFailSnackBar(failMessage = event.failMessage))
setState(currentUiState.copy(loginStatus = LoginContract.LoginStatus.Fail))
setEffect{LoginContract.LoginEffect.ShowFailSnackBar(failMessage = event.failMessage)}

Choose a reason for hiding this comment

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

저는 바로 Toast 메시지를 SideEffect로 보여줬는데 이렇게 하는게 더 맞는 것 같아요. 다음에는 할 때 적용해 보게습니다.

data class Error(val message: String? = null) : HomeUiState()

data object Idle : HomeUiState()
}

Choose a reason for hiding this comment

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

UiState 구성을 단순히 데이터 말고 상태들을 넣어서 구현하셨네요. 이렇게 하면 Loading, Error에 맞게 더 유연하게 코드를 쓸수 있을 것 같아요. 짱 ☺️

Copy link

Choose a reason for hiding this comment

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

인정합니다 저도 이부분은 항상 제너릭 타입으로 가져갔던 것 같은데 이런 방법으로도 다양하게 접근해볼수있구나 느끼고 갑니다 ...ㅠㅠ 짱짱

LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
homeViewModel.setCurrentBannerPage(page)
when (homeUiState) {

Choose a reason for hiding this comment

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

이 부분은 LaunchedEffect에 안들어가 있는 건가요??

)
}

else -> Unit

Choose a reason for hiding this comment

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

Loading, Error, Idle 인 상황에도 화면을 그려주는 게 좋을 것 같습니다.
아무런 화면도 안나오는 안드로이드 앱을 상상해보세요. 사용자는 앱을 사용하지 못하므로 버그로 보일거에요.

val editorRecommendedList: List<String> = immutableListOf(),
val todayTopRankingList: List<String> = immutableListOf(),
val homeStatus: HomeStatus = HomeStatus.Loading
val homeInitialState: HomeUiState = HomeUiState.Idle,

Choose a reason for hiding this comment

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

이 값은 계속 변화하는데요. initialState 라는 변수명이 맞는지 고민해볼 필요가 있어보입니다.

Comment on lines +21 to +24
LaunchedEffect(homeUiState) {
if(homeState.homeInitialState == HomeContract.HomeUiState.Idle) {
homeViewModel.setHomeImgList()
}

Choose a reason for hiding this comment

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

LaunchedEffect(Unit) 에서 호출하는 것으로도 충분했던 것 같은데요.
homeInitialState를 비교해서 호출하도록 바꾸신 이유가 무엇일까요?

Comment on lines +57 to +63
setState {
copy(
homeInitialState = HomeContract.HomeUiState.Success(
bannerImgList = mockBannerItem,
editorRecommendedList = mockEditorRecommendedItem,
todayTopRankingList = mockTodayTopRankingItem
)

Choose a reason for hiding this comment

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

제 생각에 Loading, Success, Error 케이스를 구분하셨으니 아래처럼 접근해보고 테스트해보시면 좋을 것 같습니다.

viewModelScope.launch {
  setLoading() // loading 중으로 state를 갱신합니다.
  delay(2000) // 임의로 로딩 중임을 표시해봅니다.
  try {
    val data = someFunction()
    setSuccess(data) // success로 갱신합니다.
  } catch (..) {
    setError(..) // error 로 갱신합니다.
  }
}

someFunction 함수가 성공하도록, 그리고 실패하도록 수정해보면서 화면이 어떻게 보이는지 테스트해보시면 좋을 듯 합니다. 어색한 부분은 없는지 등을 체크해보셔요.

Copy link

@hyeeum hyeeum left a comment

Choose a reason for hiding this comment

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

고생하셨어요..이거 마지막 코리인거죠? (눈물) 그동안 함께 과제해서 너무 감사했어요
지은언니 코드들을 보면서 참 꼼꼼하다고 생각했었습니다
언니가 to reviewer에서 언급한 내용을 생각하고있는데 마땅한 방법이 생각나면 바로 리뷰 달러올게요!!!

data class Error(val message: String? = null) : HomeUiState()

data object Idle : HomeUiState()
}
Copy link

Choose a reason for hiding this comment

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

인정합니다 저도 이부분은 항상 제너릭 타입으로 가져갔던 것 같은데 이런 방법으로도 다양하게 접근해볼수있구나 느끼고 갑니다 ...ㅠㅠ 짱짱

Copy link
Contributor

@jihyunniiii jihyunniiii left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 ~

}

is LoginContract.LoginEvent.PasswordVisibilityChanged -> {
setState(currentUiState.copy(showPassword = !currentUiState.showPassword))
setState{ copy(showPassword = !currentUiState.showPassword) }
Copy link
Contributor

Choose a reason for hiding this comment

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

얘도 인자로 받아와도 좋을 것 같아요. handleEvent 함수 내에서는 단순히 state를 변경하는 작업만 담당할 수 있게요.

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.

[WEEK7] 7주차 필수과제 구현
5 participants