Skip to content

Feat/week xml 06#11

Open
hyoeunjoo wants to merge 16 commits intodevelop-xmlfrom
feat/week-xml-06
Open

Feat/week xml 06#11
hyoeunjoo wants to merge 16 commits intodevelop-xmlfrom
feat/week-xml-06

Conversation

@hyoeunjoo
Copy link
Collaborator

📌𝘐𝘴𝘴𝘶𝘦𝘴

  • closed #

📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯

  • 새롭게 폴더링
  • 코루틴 사용
  • ViewModel로 분리

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

week06.mp4

💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴

@hyoeunjoo hyoeunjoo self-assigned this May 24, 2024
Copy link
Member

@chanubc chanubc left a comment

Choose a reason for hiding this comment

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

언제 이렇게 성장하셨죠?
진짜 잘하시네요ㄷㄷ

Comment on lines 10 to 33
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
Copy link
Member

Choose a reason for hiding this comment

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

와 진짜 폼 미쳤네요ㄷㄷ

Comment on lines +14 to +16
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
Copy link
Member

Choose a reason for hiding this comment

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

loggingInterceptor 달아보셨으면 HeaderInterceptor도 해보시는거 추천드립니다ㅎㅎ

Comment on lines 6 to 9
interface AuthRepository {
suspend fun logIn(authData: AuthData): Result<Response<Unit>>
suspend fun signUp(authData: AuthData): Result<Response<Unit>>
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

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

아니 이사람 뭐야?


import android.provider.ContactsContract.CommonDataKinds.Phone

data class AuthData(
Copy link
Member

@chanubc chanubc May 25, 2024

Choose a reason for hiding this comment

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

Suggested change
data class AuthData(
data class AuthEntity(

domain 영역의 data class는 보통 Entity로 네이밍 지어요

각 영역별 네이밍(보통 이렇게 지어요)

  1. ui : model
  2. domain : entity
  3. data : dto

class LoginViewModel(
private val authRepository: AuthRepository
) : ViewModel() {
val loginResult: MutableLiveData<Boolean> = MutableLiveData()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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> = _loginResult

Copy link
Member

Choose a reason for hiding this comment

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

내부에서 값을 변경할때는 _loginResult.value = 값
이런식으로 사용하시면 됩니다!

외부에서는 loginResult를 사용하구요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

val loginResult: LiveData get() = _loginResult 를 사용하면 _loginResult가 변경될때마다 loginResult에 변경된 값을 넣어줍니다. 동적으로 값을 가져올 수 있다는 장점이 있습니다.

val loginResult: LiveData = _loginResult
_loginResult의 값을 초기화 시점에 loginResult에 할당합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

[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()
Copy link
Member

Choose a reason for hiding this comment

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

또 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에 대해서 알아보면 좋을것 같아요!

Comment on lines +8 to +18
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
Copy link
Member

Choose a reason for hiding this comment

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

아니 이거 어디서 배우셨죠?ㄷㄷ

Copy link
Member

Choose a reason for hiding this comment

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

이게 제가 말씀드렸던 의존성 수동 주입입니다!

Copy link
Member

Choose a reason for hiding this comment

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

뷰모델 펙토리까지...ㄷㄷ

Comment on lines 8 to 18
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() }
Copy link
Member

Choose a reason for hiding this comment

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

사용도 정확하네욯

Comment on lines +33 to +37
val error = response.errorBody()?.string()
val jsonObject = error?.let { JSONObject(it) }

val message = jsonObject?.getString("message")
errorMessage.postValue(message.toString())
Copy link
Member

Choose a reason for hiding this comment

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

json 파싱 좋습니다! 이러한 json파싱도 data영역에서 하면 좋겠죠?

-tmi 안봐도 됨-
또한 서버 응답 실패시, 내 예상대로 json형태로 오지 않는 경우도 있습니다! (ex. 멀티파트-이미지 전송)
이런 경우 앞서 말씀드린 json 파싱 오류가 나겠죠? 이러한 오류도 throw가능합니다! 개발자 입장에서는 error handling이 용이해지죠(물론 이런건 사용자가 확인할 에러메세지는 아니겠지만요)

Comment on lines +9 to +19
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
Copy link
Member

Choose a reason for hiding this comment

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

이러한 ViewModelProvider.Factory를 상속받는 Factory들은 각 뷰모델 별로 만들 필요는 없습니다!
하나의 ViewModel Class만을 만들고
if else 혹은 when문을 통해 각 뷰모델과 repository 별로 분기처리 하면 됩니다!

그냥 else if문 하나 더 추가해서 같은 방식으로 각 뷰모델 프로바이더 별로 똑같이 선언해 주면 됩니다.

한번 해보고 안되면 질문주세요

Copy link
Member

@OliviaYJH OliviaYJH left a comment

Choose a reason for hiding this comment

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

닫을 이슈 번호 작성 안했어요!
그리고 완전 성장하셨네요!!👍👍
완전 수고하셨습니당!

import com.sopt.now.domain.model.AuthData
import retrofit2.Response

class AuthRepoImpl(
Copy link
Member

Choose a reason for hiding this comment

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

오호 레포지토리 패턴 이번 세미나에서 배웠는데 벌써 적용하셨군욥!! 저도 적용해봐야겠어요!

private fun getLogInRequestDto(): RequestLogInDto {
private fun getLoginData(): AuthData {
val id = binding.etId.text.toString()
val password = binding.pw2.text.toString()
Copy link
Member

Choose a reason for hiding this comment

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

pw2를 조금 더 의미있는 아이디명으로 바꾸면 좋을 것 같아요!

Comment on lines 22 to 26
val userId = response.headers()["Location"]
userId?.let {
[email protected] =it
loginResult.value = true
}
Copy link
Member

Choose a reason for hiding this comment

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

오홍 이거는 몰랐던 내용이에요! 새롭게 알아가요!

Copy link
Member

@cacaocoffee cacaocoffee left a comment

Choose a reason for hiding this comment

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

수고하셨어요! 엄청 열심히 하는게 느껴지네요.. 팩토리에 레포지토리 패턴까지.. 멋있어요!

Comment on lines 31 to 32


Copy link
Member

Choose a reason for hiding this comment

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

여기 줄간격 하나 줄여주시면 좋을거 같아요

Copy link
Member

Choose a reason for hiding this comment

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

레포지토리까지 쓰다니.. 폼미쳤다

Comment on lines 22 to 26
val userId = response.headers()["Location"]
userId?.let {
[email protected] =it
loginResult.value = true
}
Copy link
Member

Choose a reason for hiding this comment

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

저두 몰랐던 내용 새롭게 알아가요!

Comment on lines +8 to +18
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
Copy link
Member

Choose a reason for hiding this comment

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

뷰모델 펙토리까지...ㄷㄷ

Comment on lines 62 to 66
return AuthData(
id = id,
pw = password,
name = nickname,
phone = phoneNumber
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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()

이렇게 불필요한 변수 생성없이 사용하면 코드 줄일 수 있을거 같아요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants