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] Week4 필수과제 #7

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

[Feat] Week4 필수과제 #7

wants to merge 24 commits into from

Conversation

chrin05
Copy link
Contributor

@chrin05 chrin05 commented Nov 15, 2024

Related issue 🛠

Work Description ✏️

  • 회원가입 api 연결
  • 로그인 api 연결
  • 취미 입력란 추가
  • 취미 조회 api 연결

Screenshot 📸

https://youtu.be/-tS9yaWhOpo

Uncompleted Tasks 😅

  • 심화과제
  • 필수과제

To Reviewers 📢

아직 MVVM을 정확하게 이해하지 못한 거 같아서 이것저것 수정해보긴 했는데 완벽하진 않은 것 같습니다😅 많은 피드백 부탁드립니당..

Copy link

@KkamSonLee KkamSonLee left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

전체적으로 이야기하고 싶은 부분이 서버와의 소통에 대해서 공통적인 코드들이 많이 작성 되는 것 같아요, viewModel 에서 api 요청을 할 수 있는 케이스가 정말 많아지게 된다면 그에 따른 변수들과 상태 코드용 필드 변수가 많아질텐데 아마 그럴수록 문제가 생겼을 때 찾아내고 수정 하는 데에 시간을 많이 쏟을 것 같습니다!

이번 기회에 dataSource, respository 등의 개념을 통해서 데이터를 불러오는(서버, preference) 동작에 대해서 viewModel 에서는 단순히 가져오는 동작만 되도록 수정하면 좋겠습니다! 앞으로 더 복잡한 코드들을 쓰시게 될거라 한 번 코드 정리하면 좋을 것 같아요!

Comment on lines +23 to +29
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
}

Choose a reason for hiding this comment

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

retrofit 객체는 전역으로 써야할 일이 있을까요?? 그게 아니라면 private 처리 해야할 것 같습니다!

response: Response<ResponseMyHobby>
) {
if (response.isSuccessful) {
response.body()?.result?.let { userViewModel.updateHobby(it.hobby) }

Choose a reason for hiding this comment

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

result 가 null 일 때에도 에러 처리 동작이 있으면 좋겠어요!

Comment on lines +24 to +37
private val _preferenceUserName = MutableStateFlow("")
private val _preferencePassword = MutableStateFlow("")
private val _preferenceHobby = MutableStateFlow("")
private val _token = MutableStateFlow("")

val preferenceEmail = _preferenceEmail.asStateFlow()
private val _isSignInSuccessful = MutableStateFlow(false)
private val _errorMessage = MutableStateFlow("")

val preferenceUserName = _preferenceUserName.asStateFlow()
val preferencePassword = _preferencePassword.asStateFlow()
val preferenceHobby = _preferenceHobby.asStateFlow()
val preferenceToken = _token.asStateFlow()
val isSignInSuccessful = _isSignInSuccessful.asStateFlow()
val errorMessage = _errorMessage.asStateFlow()

Choose a reason for hiding this comment

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

backing properties 와 실제 필드는 같이 위치해야 알아보기 더 쉬울 것 같아요!(_preferenceUserName, preferenceUserName)

Comment on lines +39 to +42
private var preferencesUserName = ""
private var preferencesPassword = ""
private var preferencesHobby = ""
private var token = ""

Choose a reason for hiding this comment

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

안 쓰는 변수나 함수는 지워주세요!

})
}

private fun handleError(response: Response<ResponseSignIn>) {

Choose a reason for hiding this comment

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

해당 함수는 response.isSuccessful == false 일 때에만 실행하는 데 onFailure도 해당 error를 handle 할 여지가 있을 것 같아요!

}
403 -> "비밀번호가 일치하지 않습니다."
404 -> "유효하지 않은 경로로 요청하셨습니다."
else -> "서버 오류가 발생했습니다. 상태 코드: ${response.code()}"

Choose a reason for hiding this comment

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

status code 중 400, 403, 404 에러 제외를 해도 서버 오류라고만 할 순 없을 것 같아요!

Comment on lines +21 to +22
private val _userState = mutableStateOf<ResponseSignIn?>(null)
val userState: State<ResponseSignIn?> get() = _userState

Choose a reason for hiding this comment

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

해당 필드는 값을 set 하지만 사용을 안 하는 것 같아요 제거하거나 사용하는 방식으로 변경하면 좋을 것 같습니다!

import retrofit2.Response

class SignUpViewModel : ViewModel() {
private val userService by lazy { ServicePool.userService }

Choose a reason for hiding this comment

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

해당 Service 객체도 생성자에서 주입 받으면 좋을 것 같습니다! ref: DatastoreRepository

Copy link

@0se0 0se0 left a comment

Choose a reason for hiding this comment

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

4주차도 너무 수고하셨습니다!
옆에 리뷰어와 어사인 설정 해주심 더 좋을 거 같아요~!!
합세 파이팅 🍀

Comment on lines +6 to +16
@Serializable
data class ResponseSignUp(
@SerialName("result")
val result: ResponseSignUpResult,
) {
@Serializable
data class ResponseSignUpResult(
@SerialName("no")
val no: Int,
)
}
Copy link

Choose a reason for hiding this comment

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

signin response와 signupresponse를 합쳐서 사용할 수 있는 방법도 있으니 참고해보시면 좋을 거 같아요!

Copy link
Member

Choose a reason for hiding this comment

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

혹시 어떤 방법인지 여쭤봐도 될까요? Response 받는 값이 달라서 다르지 않나요?!

Comment on lines +17 to +21
val userProfileImg: Int = R.drawable.img_sample
private val _viewHistory = mutableListOf<ContentItem>()
val viewHistory: List<ContentItem> get() = _viewHistory
private val _interestContent = mutableListOf<ContentItem>()
val interestContent: List<ContentItem> get() = _interestContent
Copy link

Choose a reason for hiding this comment

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

state로 관리해보아도 좋을 거 같아요!

Comment on lines +54 to +57
val userName by userViewModel.preferenceUserName.collectAsStateWithLifecycle()
val password by userViewModel.preferencePassword.collectAsStateWithLifecycle()
val isSignInSuccessful by userViewModel.isSignInSuccessful.collectAsStateWithLifecycle()
val errorMessage by userViewModel.errorMessage.collectAsStateWithLifecycle()
Copy link

Choose a reason for hiding this comment

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

여기도 state로 관리해보면 더 좋을 거 같아요!
하나의 state로 관리하면 상태 관리가 한 곳에서 이루어져 전체 상태를 쉽게 파악하거나 나중에 수정할 때 더 좋을 거 같습니당

Copy link
Member

@Roel4990 Roel4990 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 +6 to +10
@Serializable
data class ResponseError(
@SerialName("code")
val code: String
)
Copy link
Member

Choose a reason for hiding this comment

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

에러 response 는 이렇게 받아오면 좋겠군요..!

Comment on lines +6 to +16
@Serializable
data class ResponseMyHobby(
@SerialName("result")
val result: ResponseMyHobbyResult,
) {
@Serializable
data class ResponseMyHobbyResult(
@SerialName("hobby")
val hobby: String,
)
}
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 data class 내부에 data class 를 넣어도 되는군요

Comment on lines +6 to +16
@Serializable
data class ResponseSignUp(
@SerialName("result")
val result: ResponseSignUpResult,
) {
@Serializable
data class ResponseSignUpResult(
@SerialName("no")
val no: Int,
)
}
Copy link
Member

Choose a reason for hiding this comment

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

혹시 어떤 방법인지 여쭤봐도 될까요? Response 받는 값이 달라서 다르지 않나요?!

Copy link

@ThirFir ThirFir left a comment

Choose a reason for hiding this comment

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

4주차도 과제 수고 많으셨습니다 ~!!

val myViewModel: MyViewModel = viewModel()
val viewHistory = myViewModel.viewHistory
val interestContent = myViewModel.interestContent
myViewModel.getMyHobby(userViewModel)
Copy link

Choose a reason for hiding this comment

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

뷰모델의 함수를 Composable 함수에서 직접 호출하면, 리컴포지션마다 getMyHobby가 재호출될 가능성이 있으므로 호출 위치를 옮겨주면 안전할 것 같습니다 !
예를 들면 viewModel의 init { ... } 문을 사용할 수도 있을 것 같아요 !!

Copy link
Contributor

Choose a reason for hiding this comment

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

init 블록에서 무거운 작업(서버 통신, 데이터베이스 쿼리 등과 같은,,)을 진행하는 것이 좋지 않고, 가벼운 초기화 로직만 들어가는 게 좋습니다.
init 블록에서 가벼운 초기화 로직만 작성해야 하는 이유는 여러 가지가 있는데요,, 일단 첫번째로 init 블럭은 사실상 생성자의 일부로 볼 수 있어서 비동기로 처리되어야 하는 로직을 작성하는 게 좋지 않습니다. 그리고 init 블럭에서 함수를 호출하면 그 시점이 앱의 다른 생명주기 이벤트와 충돌할 수 있어 문제가 됩니다. (예를 들어,, 뷰모델이 액티비티보다 먼저 초기화 되었는데 그 부분에서 UI를 업데이트 시키는 경우 -> 이로 인한 NPE) 또, 뷰모델에서 의존성 주입을 하는 경우에는 의존성이 주입되기 전에 함수가 호출될 수 있어요!

추가로 저도 리뷰 달다가 본 글인데 (종명 오빠 땡큐 ㅎ.ㅎ) 이 글 읽어보셔도 좋을 것 같네요 ~

onValueChange = { email = it }
value = userName,
placeholderText = "사용자 이름",
onValueChange = { userViewModel.updateUserName(it) }
Copy link

Choose a reason for hiding this comment

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

Suggested change
onValueChange = { userViewModel.updateUserName(it) }
onValueChange = userViewModel::updateUserName

이렇게 줄여서 사용해보는 것도 좋을 것 같아요 !

)
Spacer(modifier = Modifier.height(6.dp))
GrayTextField(
value = password,
placeholderText = stringResource(R.string.password),
isPassword = true,
passwordHidden = passwordHidden,
onValueChange = { password = it },
onValueChange = { userViewModel.updatePassword(it) },
Copy link

Choose a reason for hiding this comment

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

여기도!

@chrin05 chrin05 self-assigned this Nov 17, 2024
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.

고생하셨습니다! 합동 세미나도 파이팅!

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.

왜 리뷰가 날라갔징 ㅜㅜ 합세 파이띵

Comment on lines +7 to +10
data class ResponseSignIn(
@SerialName("result")
val result: ResponseSignInResult,
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

공통되는 부분을 BaseResponse로 만들어도 좋겠네요!


@GET("/user/my-hobby")
fun getMyHobby(
@Header("token")
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서 넣는 게 아니라 인터셉터를 통해 넣을 수도 있습니다!

val myViewModel: MyViewModel = viewModel()
val viewHistory = myViewModel.viewHistory
val interestContent = myViewModel.interestContent
myViewModel.getMyHobby(userViewModel)
Copy link
Contributor

Choose a reason for hiding this comment

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

init 블록에서 무거운 작업(서버 통신, 데이터베이스 쿼리 등과 같은,,)을 진행하는 것이 좋지 않고, 가벼운 초기화 로직만 들어가는 게 좋습니다.
init 블록에서 가벼운 초기화 로직만 작성해야 하는 이유는 여러 가지가 있는데요,, 일단 첫번째로 init 블럭은 사실상 생성자의 일부로 볼 수 있어서 비동기로 처리되어야 하는 로직을 작성하는 게 좋지 않습니다. 그리고 init 블럭에서 함수를 호출하면 그 시점이 앱의 다른 생명주기 이벤트와 충돌할 수 있어 문제가 됩니다. (예를 들어,, 뷰모델이 액티비티보다 먼저 초기화 되었는데 그 부분에서 UI를 업데이트 시키는 경우 -> 이로 인한 NPE) 또, 뷰모델에서 의존성 주입을 하는 경우에는 의존성이 주입되기 전에 함수가 호출될 수 있어요!

추가로 저도 리뷰 달다가 본 글인데 (종명 오빠 땡큐 ㅎ.ㅎ) 이 글 읽어보셔도 좋을 것 같네요 ~

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.

[Feat] 4주차 필수 과제
6 participants