Conversation
| class AuthRepoImpl( | ||
| private val authService: AuthService, | ||
| ) : AuthRepository { | ||
| override suspend fun logIn(authData: AuthData): Result<Response<Unit>> = runCatching { | ||
| authService.logIn( | ||
| request = RequestLogInDto( | ||
| authenticationId = authData.id, | ||
| password = authData.pw | ||
| ) | ||
| ) | ||
| } | ||
| override suspend fun signUp(authData: AuthData): Result<Response<Unit>> = runCatching { | ||
| authService.signUp( | ||
| RequestSignUpDto( | ||
| authenticationId = authData.id, | ||
| password = authData.pw, | ||
| nickname = authData.name ?: "", | ||
| phone = authData.phone ?: "" | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
|
|
||
| } No newline at end of file |
| val loggingInterceptor = HttpLoggingInterceptor().apply { | ||
| level = HttpLoggingInterceptor.Level.BODY | ||
| } |
There was a problem hiding this comment.
loggingInterceptor 달아보셨으면 HeaderInterceptor도 해보시는거 추천드립니다ㅎㅎ
| interface AuthRepository { | ||
| suspend fun logIn(authData: AuthData): Result<Response<Unit>> | ||
| suspend fun signUp(authData: AuthData): Result<Response<Unit>> | ||
| } No newline at end of file |
|
|
||
| import android.provider.ContactsContract.CommonDataKinds.Phone | ||
|
|
||
| data class AuthData( |
There was a problem hiding this comment.
| data class AuthData( | |
| data class AuthEntity( |
domain 영역의 data class는 보통 Entity로 네이밍 지어요
각 영역별 네이밍(보통 이렇게 지어요)
- ui : model
- domain : entity
- data : dto
| class LoginViewModel( | ||
| private val authRepository: AuthRepository | ||
| ) : ViewModel() { | ||
| val loginResult: MutableLiveData<Boolean> = MutableLiveData() |
There was a problem hiding this comment.
| val loginResult: MutableLiveData<Boolean> = MutableLiveData() | |
| private val _loginResult: MutableLiveData<Boolean> = MutableLiveData() | |
| val loginResult: LiveData<Boolean> get() = _loginResult |
그런데 여기서 수정해야 할 부분이 하나 더 존재합니다. 바로 MutableLiveData를 구독하는 부분인데요, Kotlin에서는 기본적으로 불변성을 지향합니다. ViewModel 내에서는 서버통신 등의 발행객체의 내부 상태를 변경해야하는 일이 있지만, 외부 객체(Activity, Fragment)에서는 이를 변경할 이유도 없고 오히려 변경한다면 예상치 못한 일들(SideEffect)이 발생할 수도 있습니다.
따라서 이런 발행객체를 사용하는 경우 상태를 변경할 수 있는 객체는 private으로, 상태를 공개하는 객체는 불변객체로 공개해야합니다. 이런 기법을 **Backing Property**라고 합니다.
추가로 get() = 를 사용하지 않고 = 으로 값을 바로 할당하는 방법이 있는데 두 코드의 차이점이 무엇인지 찾아보고 댓글로 남겨주세요!
val loginResult: LiveData<Boolean> get() = _loginResult
val loginResult: LiveData<Boolean> = _loginResultThere was a problem hiding this comment.
내부에서 값을 변경할때는 _loginResult.value = 값
이런식으로 사용하시면 됩니다!
외부에서는 loginResult를 사용하구요!
There was a problem hiding this comment.
val loginResult: LiveData get() = _loginResult 를 사용하면 _loginResult가 변경될때마다 loginResult에 변경된 값을 넣어줍니다. 동적으로 값을 가져올 수 있다는 장점이 있습니다.
val loginResult: LiveData = _loginResult
_loginResult의 값을 초기화 시점에 loginResult에 할당합니다.
There was a problem hiding this comment.
[MutableLiveData]
LiveData는 Observer 패턴을 따르며 데이터의 변경이 일어날 때마다 콜백을 받습니다.
이때, LiveData의 값을 변경하게 해주는 함수는 setValue와 postValue가 있습니다.
두 함수는 protected로 되어있기에, 외부에서 값을 변경해주기 위해서는 LiveData를 상속받는 MutableLiveData를 사용해야 합니다.
[email protected] =it
loginResult.value = true
backing property를 사용하여 loginResult를 바꾸니 위의 부분에 문제가 생겼습니다.
MutableLiveData의 value는 메인 스레드에서만 사용할 수 있으므로 백그라운드에서 안전하게 값을 보낼 수 있는 postValue를 사용합니다.
| private val authRepository: AuthRepository | ||
| ) : ViewModel() { | ||
| val loginResult: MutableLiveData<Boolean> = MutableLiveData() | ||
| val errorMessage: MutableLiveData<String> = MutableLiveData() |
There was a problem hiding this comment.
또 Uistate를 사용하게 되면 loginResult와 errorMesage의 변수를 합쳐서 사용 가능합니다!
sealed class LoginState {
data object Loading : LoginState()
data class Success(val data: AuthData) : LoginState()
data class Failure(val errorMessage: String) : LoginState()
}해당 seale class를 만들고 LiveData의 파라미터에
private val _loginResult: MutableLiveData<LoginState<Boolean>> = MutableLiveData()
val loginResult: LiveData<LoginState<Boolean>> get() = _loginResult액티비티에선 observe하고 각 state를 when절로 분기처리 후 is를 통해 smart cast한 다음, 각 state(success, fail) 등등 필요한 작업을 하면 됩니다
이런식으로 선언해서 사용하시면 됩니다! 자세한 사항들은 uistate에 대해서 알아보면 좋을것 같아요!
| class LoginViewModelFactory : ViewModelProvider.Factory{ | ||
| override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
| if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { //modelClass가 LoginViewModel인지 확인 | ||
| val repository = | ||
| AuthRepoImpl(ServicePool.authService)//authService를 인자로 받아서 Repository인스턴스 생성 | ||
| return LoginViewModel(repository) as T | ||
| } else { | ||
| throw IllegalArgumentException("Failed to create ViewModel: ${modelClass.name}") | ||
| } | ||
| } | ||
| } No newline at end of file |
| import androidx.lifecycle.Observer | ||
| import androidx.lifecycle.ViewModelProvider | ||
| import com.sopt.now.R | ||
| import com.sopt.now.databinding.ActivitySignupBinding | ||
| import com.sopt.now.dto.SignUp.RequestSignUpDto | ||
| import com.sopt.now.domain.model.AuthData | ||
| import com.sopt.now.presentation.Login.LoginActivity | ||
| import com.sopt.now.presentation.User.SignUpViewModelFactory | ||
|
|
||
|
|
||
| class SignUpActivity : AppCompatActivity() { | ||
| private lateinit var binding: ActivitySignupBinding | ||
| private lateinit var viewModel: SignUpViewModel | ||
| private val viewModel: SignUpViewModel by viewModels { SignUpViewModelFactory() } |
| val error = response.errorBody()?.string() | ||
| val jsonObject = error?.let { JSONObject(it) } | ||
|
|
||
| val message = jsonObject?.getString("message") | ||
| errorMessage.postValue(message.toString()) |
There was a problem hiding this comment.
json 파싱 좋습니다! 이러한 json파싱도 data영역에서 하면 좋겠죠?
-tmi 안봐도 됨-
또한 서버 응답 실패시, 내 예상대로 json형태로 오지 않는 경우도 있습니다! (ex. 멀티파트-이미지 전송)
이런 경우 앞서 말씀드린 json 파싱 오류가 나겠죠? 이러한 오류도 throw가능합니다! 개발자 입장에서는 error handling이 용이해지죠(물론 이런건 사용자가 확인할 에러메세지는 아니겠지만요)
| class SignUpViewModelFactory :ViewModelProvider.Factory { | ||
| override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
| if (modelClass.isAssignableFrom(SignUpViewModel::class.java)) { | ||
| val repository = | ||
| AuthRepoImpl(ServicePool.authService)//authService를 인자로 받아서 Repository인스턴스 생성 | ||
| return SignUpViewModel(repository) as T | ||
| } else { | ||
| throw IllegalArgumentException("Failed to create ViewModel: ${modelClass.name}") | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
이러한 ViewModelProvider.Factory를 상속받는 Factory들은 각 뷰모델 별로 만들 필요는 없습니다!
하나의 ViewModel Class만을 만들고
if else 혹은 when문을 통해 각 뷰모델과 repository 별로 분기처리 하면 됩니다!
그냥 else if문 하나 더 추가해서 같은 방식으로 각 뷰모델 프로바이더 별로 똑같이 선언해 주면 됩니다.
한번 해보고 안되면 질문주세요
OliviaYJH
left a comment
There was a problem hiding this comment.
닫을 이슈 번호 작성 안했어요!
그리고 완전 성장하셨네요!!👍👍
완전 수고하셨습니당!
| import com.sopt.now.domain.model.AuthData | ||
| import retrofit2.Response | ||
|
|
||
| class AuthRepoImpl( |
There was a problem hiding this comment.
오호 레포지토리 패턴 이번 세미나에서 배웠는데 벌써 적용하셨군욥!! 저도 적용해봐야겠어요!
| private fun getLogInRequestDto(): RequestLogInDto { | ||
| private fun getLoginData(): AuthData { | ||
| val id = binding.etId.text.toString() | ||
| val password = binding.pw2.text.toString() |
There was a problem hiding this comment.
pw2를 조금 더 의미있는 아이디명으로 바꾸면 좋을 것 같아요!
| val userId = response.headers()["Location"] | ||
| userId?.let { | ||
| [email protected] =it | ||
| loginResult.value = true | ||
| } |
cacaocoffee
left a comment
There was a problem hiding this comment.
수고하셨어요! 엄청 열심히 하는게 느껴지네요.. 팩토리에 레포지토리 패턴까지.. 멋있어요!
|
|
||
|
|
| val userId = response.headers()["Location"] | ||
| userId?.let { | ||
| [email protected] =it | ||
| loginResult.value = true | ||
| } |
| class LoginViewModelFactory : ViewModelProvider.Factory{ | ||
| override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
| if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { //modelClass가 LoginViewModel인지 확인 | ||
| val repository = | ||
| AuthRepoImpl(ServicePool.authService)//authService를 인자로 받아서 Repository인스턴스 생성 | ||
| return LoginViewModel(repository) as T | ||
| } else { | ||
| throw IllegalArgumentException("Failed to create ViewModel: ${modelClass.name}") | ||
| } | ||
| } | ||
| } No newline at end of file |
| return AuthData( | ||
| id = id, | ||
| pw = password, | ||
| name = nickname, | ||
| phone = phoneNumber |
There was a problem hiding this comment.
| return AuthData( | |
| id = id, | |
| pw = password, | |
| name = nickname, | |
| phone = phoneNumber | |
| return AuthData( | |
| id = binding.etIdSignup.text.toString(), | |
| pw = binding.etPwSignup.text.toString(), | |
| name = binding.etNicknameSignup.text.toString(), | |
| phone = binding.etMbtiSignup.text.toString() |
이렇게 불필요한 변수 생성없이 사용하면 코드 줄일 수 있을거 같아요
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
week06.mp4
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴