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/#12] 7주차 과제 구현 #13

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions presentation/src/main/java/org/sopt/and/presentation/my/MyScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import org.sopt.and.presentation.component.ContentsView
import org.sopt.and.presentation.component.PairTextView
import org.sopt.and.presentation.delegate.NetworkState
import org.sopt.and.presentation.extension.noRippleClickable
import org.sopt.and.presentation.my.contract.MyEvent
import org.sopt.and.presentation.my.sideeffect.MySideEffect
import org.sopt.and.presentation.my.viewmodel.MyViewModel
import org.sopt.and.presentation.ui.theme.FirstGrey
Expand All @@ -55,33 +56,33 @@ fun MyScreen(
modifier: Modifier = Modifier,
viewModel: MyViewModel = hiltViewModel()
) {
LaunchedEffect(viewModel.state) {
if(viewModel.state.value.hobby == DEFAULT_STRING) viewModel.getMyHobby()
LaunchedEffect(viewModel.uiState) {
if(viewModel.uiState.value.hobby == DEFAULT_STRING) viewModel.getMyHobby()
}

Column(
modifier = modifier.fillMaxSize()
) {

val state by viewModel.state.collectAsStateWithLifecycle()
val state by viewModel.uiState.collectAsStateWithLifecycle()

val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() }

LaunchedEffect(viewModel.intent, lifecycleOwner){
viewModel.intent.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect{ intent ->
when(intent) {
MySideEffect.Logout -> {
LaunchedEffect(viewModel.sideEffect, lifecycleOwner){
viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect{ sideEffect ->
when(sideEffect) {
MySideEffect.NavigateToSignIn -> {
viewModel.clearUserPreference()
snackBarHostState.showSnackbar(
message = context.getString(R.string.my_logout_text)
)
onLogout()
}
is MySideEffect.SnackBar -> snackBarHostState.showSnackbar(context.getString(intent.message))
is MySideEffect.SnackBarText -> snackBarHostState.showSnackbar(intent.message)
is MySideEffect.SnackBar -> snackBarHostState.showSnackbar(context.getString(sideEffect.message))
is MySideEffect.SnackBarText -> snackBarHostState.showSnackbar(sideEffect.message)
}
}
}
Expand Down Expand Up @@ -183,7 +184,7 @@ fun MyScreen(
.align(Alignment.CenterHorizontally)
.noRippleClickable(
onClick = {
viewModel.onLogOutButtonClick()
viewModel.setEvent(MyEvent.OnLogoutButtonClick)
}
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.and.presentation.my.contract

import org.sopt.and.presentation.util.base.UiEvent

sealed class MyEvent: UiEvent {
data class GetMyHobby(val hobby: String): MyEvent()
data object OnLogoutButtonClick: MyEvent()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.sopt.and.presentation.my.model

import org.sopt.and.presentation.util.KeyUtil.DEFAULT_STRING
import org.sopt.and.presentation.util.base.UiState

data class MyState(
val hobby: String = DEFAULT_STRING
)
): UiState
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.sopt.and.presentation.my.sideeffect

import androidx.annotation.StringRes
import org.sopt.and.presentation.util.base.UiSideEffect

sealed class MySideEffect {
sealed class MySideEffect: UiSideEffect {
data class SnackBar(@StringRes val message: Int): MySideEffect()
data class SnackBarText(val message: String): MySideEffect()
data object Logout: MySideEffect()
data object NavigateToSignIn: MySideEffect()

Choose a reason for hiding this comment

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

네이밍 변경 좋네요!

}
Original file line number Diff line number Diff line change
@@ -1,59 +1,61 @@
package org.sopt.and.presentation.my.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.sopt.and.domain.exception.onError
import org.sopt.and.domain.exception.onSuccess
import org.sopt.and.domain.repository.UserRepository
import org.sopt.and.presentation.delegate.NetworkDelegate
import org.sopt.and.presentation.my.contract.MyEvent
import org.sopt.and.presentation.my.model.MyState
import org.sopt.and.presentation.my.sideeffect.MySideEffect
import org.sopt.and.presentation.sign.signin.sideeffect.SignInSideEffect
import org.sopt.and.presentation.util.base.BaseViewModel
import javax.inject.Inject

@HiltViewModel
class MyViewModel @Inject constructor(
private val userRepository: UserRepository,
private val networkDelegate: NetworkDelegate
): ViewModel() {
): BaseViewModel<MyState, MyEvent, MySideEffect>() {

private val _state = MutableStateFlow(MyState())
val state = _state.asStateFlow()
override fun createInitialState(): MyState = MyState()

private val _intent = MutableSharedFlow<MySideEffect>()
val intent = _intent.asSharedFlow()
override suspend fun handleEvent(event: MyEvent) {
when(event) {
MyEvent.OnLogoutButtonClick -> {

Choose a reason for hiding this comment

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

is를 붙이고 안붙이고의 차이는 뭔가요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getMyHobby는 data class 상태(프로퍼티)를 가지며, is로 타입 검사를 통해 특정 클래스로 매칭이 필요합니다 ! onLoginButtonClick은 data object이기 때문에 상태가 없는 단일 객체로, 고유 값으로 비교가 가능합니다!

navigateToSignIn()
}
is MyEvent.GetMyHobby -> {
setState { copy(hobby = event.hobby) }
}
}
}

val networkState get() = networkDelegate.networkState

fun getMyHobby() = viewModelScope.launch {
val token = userRepository.getToken()
userRepository.getMyHobby(token).onSuccess { result ->
_state.update {
it.copy(hobby = result.hobby)
}
setEvent(MyEvent.GetMyHobby(result.hobby))
networkDelegate.handleNetworkSuccess()
}.onError {
networkDelegate.handleGetMyHobbyError(it)
}
}

fun onLogOutButtonClick() = viewModelScope.launch {
_intent.emit(MySideEffect.Logout)
private fun navigateToSignIn() = viewModelScope.launch {
delay(100)

Choose a reason for hiding this comment

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

delay를 주는 이유가 무엇인가용??

Choose a reason for hiding this comment

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

여기도 있고 아래도 있던데 저도 궁금합니다!

Choose a reason for hiding this comment

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

저도 궁금합니당

Copy link
Contributor Author

@kangyein9892 kangyein9892 Jan 1, 2025

Choose a reason for hiding this comment

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

화면 이동 되면서 스낵바가 안 떠서 delay를 줬는데 좋은 방법은 아닌 것 같아요... 다른 방법이 있을까요 ? ㅠㅠ
제가 상태관리를 잘못했나봐요...
라고 pr 글에서 말했던.... 이유입니다.......

setSideEffect(MySideEffect.NavigateToSignIn)
}

fun clearUserPreference() = viewModelScope.launch {
userRepository.clearUserPreference()
}

suspend fun handleMyIntentError(message: String) {
_intent.emit(MySideEffect.SnackBarText(message))
fun handleMyIntentError(message: String) {
setSideEffect(MySideEffect.SnackBarText(message))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import org.sopt.and.presentation.component.WavveActionTextField
import org.sopt.and.presentation.component.WavveTextField
import org.sopt.and.presentation.delegate.NetworkState
import org.sopt.and.presentation.extension.noRippleClickable
import org.sopt.and.presentation.sign.signin.sideeffect.SignInSideEffect
import org.sopt.and.presentation.sign.signin.contract.SignInEvent
import org.sopt.and.presentation.sign.signin.contract.SignInSideEffect
import org.sopt.and.presentation.sign.signin.viewmodel.SignInViewModel
import org.sopt.and.presentation.ui.theme.FirstGrey
import org.sopt.and.presentation.ui.theme.SecondGrey
Expand All @@ -56,20 +57,21 @@ fun SignInScreen(
viewModel: SignInViewModel = hiltViewModel()
){

val state by viewModel.state.collectAsStateWithLifecycle()
val state by viewModel.uiState.collectAsStateWithLifecycle()
val context = rememberUpdatedState(LocalContext.current).value
val snackBarHostState = remember { SnackbarHostState() }

LaunchedEffect(viewModel.intent) {
viewModel.intent.collect{ intent ->
when(intent) {
SignInSideEffect.SignIn -> {
viewModel.saveUser(state.id, state.password)
LaunchedEffect(viewModel.sideEffect) {
viewModel.sideEffect.collect{ sideEffect ->
when(sideEffect) {
is SignInSideEffect.SnackBar -> snackBarHostState.showSnackbar(context.getString(sideEffect.message))
is SignInSideEffect.SnackBarText -> snackBarHostState.showSnackbar(sideEffect.message)
SignInSideEffect.NavigateToSignUp -> {
navigateToSignUp()
Comment on lines +68 to +70

Choose a reason for hiding this comment

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

위에도 있는 질문인데 is의 유무로 인해 어떤 차이가 있는 건가요!?

Copy link
Contributor

Choose a reason for hiding this comment

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

저도 궁금해요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#13 (comment)
요기 달았어요!
코드 써보시면 알겠지만 is가 없다면 빨간줄이 뜹니다~

}
SignInSideEffect.NavigateToMy -> {
navigateToMy()
}
SignInSideEffect.SignUp -> navigateToSignUp()
is SignInSideEffect.SnackBar -> snackBarHostState.showSnackbar(context.getString(intent.message))
is SignInSideEffect.SnackBarText -> snackBarHostState.showSnackbar(intent.message)
}
}
}
Expand All @@ -93,7 +95,9 @@ fun SignInScreen(
WavveTextField(
value = state.id,
hint = stringResource(R.string.signup_login_hint),
onValueChange = viewModel::updateId,
onValueChange = {
viewModel.setEvent(SignInEvent.OnIdChanged(it))
},
modifier = Modifier.padding(horizontal = 8.dp)
)

Expand All @@ -102,7 +106,9 @@ fun SignInScreen(
WavveActionTextField(
value = state.password,
hint = stringResource(R.string.signin_password_hint),
onValueChange = viewModel::updatePassword,
onValueChange = {
viewModel.setEvent(SignInEvent.OnPasswordChanged(it))
},
modifier = Modifier.padding(horizontal = 8.dp)
)

Expand Down Expand Up @@ -175,7 +181,7 @@ fun SignInScreen(
modifier = Modifier
.noRippleClickable(
onClick = {
viewModel.onSignUpButtonClick()
viewModel.setEvent(SignInEvent.OnSignUpButtonClick)
}
)
)
Expand Down Expand Up @@ -214,10 +220,10 @@ private suspend fun collectNetworkState(viewModel: SignInViewModel) {
// TODO 로딩 추가
}
is NetworkState.Error -> {
viewModel.handleSignInIntentError(networkState.title + networkState.msg)
viewModel.handleSignInSideEffectError(networkState.title + networkState.msg)
}
is NetworkState.Success -> {
viewModel.handleSignInIntentSuccess()
viewModel.handleSignInSideEffectSuccess()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.and.presentation.sign.signin.contract

import org.sopt.and.presentation.util.base.UiEvent

sealed class SignInEvent: UiEvent {
data class OnIdChanged(val id: String) : SignInEvent()
data class OnPasswordChanged(val password: String) : SignInEvent()
data object OnSignInButtonClick : SignInEvent()
data object OnSignUpButtonClick : SignInEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.and.presentation.sign.signin.contract

import androidx.annotation.StringRes
import org.sopt.and.presentation.util.base.UiSideEffect

sealed class SignInSideEffect: UiSideEffect {
data class SnackBar(@StringRes val message: Int) : SignInSideEffect()
data class SnackBarText(val message: String) : SignInSideEffect()
data object NavigateToSignUp: SignInSideEffect()
data object NavigateToMy: SignInSideEffect()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.sopt.and.presentation.sign.signin.model
package org.sopt.and.presentation.sign.signin.contract

import org.sopt.and.presentation.util.KeyUtil.DEFAULT_STRING
import org.sopt.and.presentation.util.base.UiState

data class SignInState(
val id: String = DEFAULT_STRING,
val password: String = DEFAULT_STRING,
)
): UiState

This file was deleted.

Loading