뷰모델에서는 베이스뷰모델의 상태,이벤트,효과를 상속 받아서 발생된 이벤트들을 어떻게 처리할지 나타내었다.
이벤트가 발생한다면 when을 통해 이벤트 분기를 시작한다.
사용자가 id,pw를 입력한다면 상태에 변화가 일어나기 때문에 setState로 상태를 재설정해준다.
그리고 유저가 버튼을 눌러 로그인을 시도한다면 effect(효과)로 toast메시지를 띄우고 화면 전환을 유도했다.
이 때 자세한 효과 구현은 스크린의 Effect()함수 안에 따로 또 구현을 한다.
class UserInfoViewModel : BaseViewModel<UserInfoContract.State, UserInfoContract.Event, UserInfoContract.Effect>() {
override fun createState(): UserInfoContract.State {
return UserInfoContract.State(id = "", password = "")
}
override fun handleEvent(event: UiEvent) {
when (event) {
is UserInfoContract.Event.UpdateId -> {
setState { copy(id = event.id) }
}
is UserInfoContract.Event.UpdatePassword -> {
setState { copy(password = event.password) }
}
is UserInfoContract.Event.PerformSignUp -> {
sendEffect { UserInfoContract.Effect.SignUpSuccess }
}
is UserInfoContract.Event.PerformLogin -> {
if (state.value.id == "123" && state.value.password == "123") {
sendEffect {
val message = "정상적인 로그인입니다."
UserInfoContract.Effect.LoginSuccess(message)
}
NavigationManager.navController.navigate("main")
}
else{
sendEffect {
val message = "존재하는 아이디나 비밀번호가 없습니다."
UserInfoContract.Effect.LoginSuccess(message)
}
}
}
}
}
}
로그인에 관련된 정보임으로 id,pw로 간단히 구성했고, 백엔드쪽에서 확정된 데이터 정보를 가지고 리팩토링 해야할 것이다.
data class State(
val id: String,
val password: String
)
여기서는 sealed class를 사용했는데, 다양한 이벤트를 다루어야 하고 보통 그 때 사용되는 게 when이다.
sealed class를 이용하는 이유는 when을 사용할 때 모든 이벤트를 안전하게 구현하기 위해서이다.
예를 들어 그냥 3개의 클래스 upbtnClick,downbtnClick,leftbtnClick이 있다고 보자.
그리고 이들은 전부 abstract class인 btnClick을 상속 받는다.
이 때 우리가 when을 통해서 btnClick을 처리하려고 할 때 sealed class를 사용하면 반드시 3개의 클릭 경우를 다 구현해야하는 것이다.
이해가 쉽게 쭉 풀어서 설명했는데 쉽게 설명한 블로그는 아래에..
https://kotlinworld.com/165
sealed class Event : UiEvent {
data class UpdateId(val id: String) : Event()
data class UpdatePassword(val password: String) : Event()
object PerformSignUp : Event()
object PerformLogin : Event()
}
뷰모델에서 발생한 이벤트에 맟춰서 sendEffect를 발생시키고
화면을 구현하는 스크린이 override해야할 effect에서 자세한 내용을 구현한다.
@Composable
override fun Effect() {
val context = LocalContext.current
LaunchedEffect(viewModel.effect) {
viewModel.effect.onEach { effect ->
when(effect) {
is UserInfoContract.Effect.LoginSuccess -> {
Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
}
is UserInfoContract.Effect.SignUpSuccess-> {
}
}
}.collect()
}
}
실제로 패턴과 클린 아키텍처를 준수하면서 구현하려고 하니 역시나 쉽지 않았다..사실 제대로 하고 있는건지도 아직은 모른다.
이미 잘 되어있는 코드를 보고 이렇게도 할 수 있구나 싶어서 하루는 그냥 아무것도 하지 않고 코드를 이해하는데만 시간을 보냈다.
그래도 로그인,회원가입에 대한 상태,이벤트,효과를 직접 짜보면서 대략적인 큰 흐름이 이해가 되고 있다!
상태
말 그대로 UI의 현재 상태를 말하는데 거의 데이터?라고 생각했다.
data class LoginState(
val username: String,
val password: String,
val isLoading: Boolean
)
이런 느낌으로 말이다.
이벤트
이벤트도 그냥 이벤트다!
사용자 클릭이나..사용자가 뭘 입력한다거나?
sealed class LoginEvent {
data class UsernameChanged(val username: String) : LoginEvent()
data class PasswordChanged(val password: String) : LoginEvent()
data class LoginBtnClick(val message: String) : LoginEvent()
}
효과
효과는 이벤트가 발생했을 때 일어나는 효과라고 생각하는 게 가장 편했다!
예를 들어 로그인 버튼을 클릭하는 이벤트가 발생한 경우 그 발생한 이벤트에 따라 성공 혹은 실패에 따라 화면이 전환되는 효과가 발생하는 것이다!!
fun handleEvent(event: LoginViewEvent) {
when (event) {
is LoginViewEvent.LoginClicked -> {
if (event.username == "admin" && event.password == "1234") {
// 로그인 성공 시 효과 발생
effect.value = LoginViewEffect.ShowSuccessMessage("로그인에 성공했습니다.")
}
}
}
}