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

[Week4] 4주차 필수 과제 #12

Merged
merged 28 commits into from
Dec 4, 2024
Merged

[Week4] 4주차 필수 과제 #12

merged 28 commits into from
Dec 4, 2024

Conversation

hyeeum
Copy link
Contributor

@hyeeum hyeeum commented Nov 14, 2024

Related issue 🛠

Work Description ✏️

[유저 등록]
- 회원가입 페이지에 진행해주세요.
- 기존에 이메일, 비밀번호를 받는 구조에서 username, password, hobby를 입력하는 구조로 변경해주세요.
- username, password, hobby가 조건을 만족하지 않는 경우 (8자 이상인 경우) 조건을 만족하지 않았다는 것을 화면에 표시해주세요. (방식은 자율입니다.)

[로그인]
- 로그인 페이지에 진행해주세요.
- 기존 이메일, 비밀번호를 받는 구조에서 username, password를 받는 구조로 변경해주세요.
- 로그인 실패, 성공 시에 알맞은 안내 문구를 유저에게 제시해주세요. (방식은 자율입니다.)

[내 취미 조회]
- MY 페이지에 진행해주세요
- 기존에 이메일을 표시했던 부분에 내 취미가 표시되도록 해주세요.

Screenshot 📸

이름 오류 비밀번호 오류 취미 오류 정상 회원가입
이름 오류 비밀번호 오류 취미 오류 정상 회원가입
로그인 오류 정상 로그인
로그인 오류 정상 로그인

취미 불러오기

To Reviewers 📢

  • 네이밍 붙이는게 참어려운 것 같아요 , signup signin을 data layer코드에 넣는게 맞는지 뭔가 하면서도 이건 아닌 것 같다는 느낌이 드는데 다른 분들의 코드를 많이 참고하면서 더 좋은 네이밍을 알아보도록 하겠습니다 : - )
  • 그리고 서버통신은 한 번 하면 감이 잡히기는하는데 체화를 하는 과정이 참 오래 걸리는 것 같아요 패키지를 분리하는 과정에서 다시 한 번 아키텍처 정리를 할 수 있었습니다..! 까먹지않도록 계속 봐야할 것 같네요

@hyeeum hyeeum added the enhancement New feature or request label Nov 14, 2024
@hyeeum hyeeum self-assigned this Nov 14, 2024
Copy link

@greedy0110 greedy0110 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 58 to 61
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()

Choose a reason for hiding this comment

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

릴리즈에는 필요없는 interceptor이므로 디버그 앱에만 추가해주심이 어떨까요

Suggested change
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) builder.addInterceptor(loggingInterceptor)
builder.addInterceptor(authInterceptor)
return builder.build()

Copy link
Contributor Author

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 27
viewModelScope.launch {
wavveRepository.getHobby().onSuccess { hobbyEntity ->
_state.value = _state.value.copy(
hobby = hobbyEntity.hobby
)
}.onFailure { }

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.

클라이언트 개발자로서 클라이언트를 고려하지 않은 점이 두드러지는 것 같네요 :( 해당 사항고려해서 적절한 이벤트를 추가하도록 하겠습니다

import javax.inject.Singleton

@Singleton
class User @Inject constructor(

Choose a reason for hiding this comment

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

모듈이 분리된 이 시점에서 이 객체는 다분히 data 혹은 repository 수준에서 관리될 친구로 보이네요.

data로 관리한다면 UserToken 정보를 디바이스에 저장하는 객체로 변모할 것이고 (현재 프로젝트 구조 상 이름은 UserDataSource, TokenDataSource ?)

repository로 관리한다면 UserToken 정보를 저장하고, 필요한 시점에 초기화 하는 인터페이스를 제공하는 객체로 활용되겠네요. (현재 프로젝트 구조에 맞는 이름은 아마도 UserRepository? AuthRepository? SignRepository)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

User 를 어떻게 분류해야할지 고민이 있었는데 말씀해주신 내용 바탕으로 고민해보고 수정해보겠습니다,감사합니다! : )

Copy link

@kamja0510 kamja0510 left a comment

Choose a reason for hiding this comment

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

역시 혜음.. 저같은 사람도 코드를 알아볼 수 있게 짜셨네요(mvi는 살짝 이해못함 ㅎ)
항상 코드리뷰 시간이 아니라 보면서 공부하는 느낌

@Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor { message ->
Log.d("Retrofit2", "CONNECTION INFO -> $message")

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.

사실 이 블럭 안에 코드가 없어도 로그캣에 인터셉터 내용이 잘 출력되지만 조금 더 로그 잘보이게 하려고 로그 커스텀 했어요 ㅋㅋ


@Serializable
data class RequestSignUpDto(
@SerialName("username")

Choose a reason for hiding this comment

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

변수명과 json key명이 같은데도 SerialName 어노테이션을 굳이 써야하나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저는 사실 그냥 습관적으로 사용했었는데요. 최근에 새롭게 알게 된 사실이 있어요.
추후에 클라에서 암호화해서 서버통신을 하게된다면 어노테이션이 없을 경우 서버가 해석을 못한다고 하더라고요!!
함께 알아가시면 좋을 것 같네요...신기방구

@SerialName("hobby")
val hobby: String
){
fun toEntity() = ResponseHobbyEntity(

Choose a reason for hiding this comment

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

신기한 문법들


fun NavController.navigateSearch(
navOptions: NavOptions
) {
navigate(Search, navOptions)
navigate(org.sopt.and.feature.search.Search, navOptions)

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.

어 그러게요 이거 뭐야

val password: String = "",
var isPasswordVisible: Boolean = false,
) {
val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty()

Choose a reason for hiding this comment

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

이 줄이 왜 중괄호 블럭으로 따로 빠져 있나요? 저 username이랑 password를 쓰려면 따로 빼야하는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

username password를 사용하기 위해서 뺀 건 아니고, username password가 바뀔때마다 isButtonEnabled도 함께 값이 변경되어야하므로 따로 뺀 거랍니다! 뷰모델에서 작성하는 방법도 있긴한데 저는 간단한 로직이라서 State안에다 뒀어요!

@@ -72,9 +94,8 @@ class SignUpViewModel @Inject constructor() : ViewModel() {
}

companion object {
private const val MIN_PASSWORD = 8
private const val MAX_PASSWORD = 20
private const val MIN_SIGNUP_LENGTH = 1

Choose a reason for hiding this comment

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

와 저는 상수화도 안하고 최소값1로 두지도 않았네요 혼나겠다

@Singleton
class User @Inject constructor(
@ApplicationContext private val context: Context
) {
private val sharedPreferences: SharedPreferences =

Choose a reason for hiding this comment

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

호옥시 datastore 안쓰시고 sharedPreferences 쓰시는 이유가 있나요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아뇨 ㅠㅠ 딱히 이유없어요... 사실 datastore안써봄

Choose a reason for hiding this comment

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

사소한 부분들도 string들을 다 바꾸셨네요!

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

LaunchedEffect(true) {

Choose a reason for hiding this comment

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

이런거 보면 Route를 쓰는게 좋을 거 같다는 생각이 드네요

Copy link

@HAJIEUN02 HAJIEUN02 left a comment

Choose a reason for hiding this comment

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

고생많았서요 혜음이~~ 클린아키텍처 적용을 해서 그런지 저랑 비슷한 부분이 많아서 보기 편했습니다 ㅎㅎ 유스케이스도 한번 사용해보셔요!


// Network
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp)

Choose a reason for hiding this comment

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

okhttp랑 retrofit처럼 같은 카테고리인 친구들은 각각 libs.versions.toml 내부에서 bundles로 묶어서 한번에 디펜던시 추가해줄 수 있서요! 그러면 더 보기 깔끔할 듯합니당

Copy link
Contributor Author

Choose a reason for hiding this comment

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

적용해보겠습니다링~!!!

tools:targetApi="31">
<activity
android:name=".main.MainActivity"
android:name=".feature.main.MainActivity"

Choose a reason for hiding this comment

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

오호 ui가 아니라 feature로 패키지를 구성하셨군요
새로운 네이밍 좋네요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저는 지난 플젝때 이렇게 했어서 자연스럽게 이렇게 네이밍을 했어요

@Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor { message ->
Log.d("Retrofit2", "CONNECTION INFO -> $message")

Choose a reason for hiding this comment

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

로그는 PR 올릴 때 삭제해주시거나 Timber로 바꿔주시면 좋을 것 같아요!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

팀버녀석 사용해볼게요!!

val password: String,
)

fun RequestSignInEntity.toDto() = RequestSignInDto(

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.

그렇네요!! 꼼꼼한 리뷰 너무 감사해요 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

구현하려다가 생각난건데 이렇게 되면 domain단에서 data를 알고있다는건데 클린 아키텍처의 목적에 부합하지 않아지는 것 아닐까요?? 이와 관련해서는 어떻게 생각하시는지도 궁금합니다!

Copy link
Contributor

@1971123-seongmin 1971123-seongmin left a comment

Choose a reason for hiding this comment

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

고생하셨습니다. 항상 잘하셔서 리뷰라기보단 많이 배워가는 것 같아요.

override suspend fun getUserHobby(): BaseResponse<ResponseUserHobbyDto> =
wavveService.getUserHobby()

}
Copy link
Contributor

Choose a reason for hiding this comment

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

깔끔 그 자체에요. 코드 너무 좋아요

@Binds
@Singleton
abstract fun bindsDataSource(myDataSourceImpl: WavveDataSourceImpl): WavveDataSource
}
Copy link
Contributor

Choose a reason for hiding this comment

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

데이터스토어 모듈을 따로 만드셨네요. 저도 분리해야겠어요.

@SerialName("token")
val token: String
){
fun toEntity() = ResponseSignInEntity(
Copy link
Contributor

Choose a reason for hiding this comment

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

objcect에 Mapper를 모아두는 방식을 안쓰고 데이터 클래스에 바로 정의하신 이유 알려주실 수 있나요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

사실 이게 습관이 된 것도 있기도하고 뭔가 파일이 더 많아지는 게 싫어서 ㅠㅠ 의 이유도 있습니다


import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import kotlinx.serialization.Serializable
import org.sopt.and.main.MainTabRoute
import org.sopt.and.feature.main.MainTabRoute

fun NavController.navigateSignUp() {
navigate(SignUp)
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.

저는 이전 플젝에서 이렇게 사용했어서 사용하긴한건데 다른 분들은 어떻게 생각하는지 궁금하네요..
개인적으로는 한 파일에서 관리해줄 수 있으니까 편하다고 느껴지기는 한 것 같아요..!

val hobby: String = "",
var isPasswordVisible: Boolean = false,
) {
val isButtonEnabled: Boolean = username.isNotEmpty() && password.isNotEmpty()
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.

사람 취향에 따라 viewmodel에서 관리하거나 state에서 관리할 수 있는데 저는 간단한 로직이라서 따로 함수 호출없이 이렇게 관리하는게 편한 것 같아요!

<string name="not_valid_input">유효하지 않은 입력입니다.</string>

<!--signin-->
<string name="signin">로그인</string>
<string name="signin_placeholder">이메일 주소 또는 아이디</string>
<string name="signin_username_placeholder">이름</string>
<string name="signin_password_placeholder">비밀번호</string>
<string name="find_id">아이디 찾기</string>
<string name="reset_password">비밀번호 재설정</string>
<string name="join">회원가입</string>
Copy link
Contributor

Choose a reason for hiding this comment

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

string 이름들이 정말 깔끔하고 직관적이네요..

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.

역시 참 깔끔하네요! 고생하셨습니다! 우리 혜음이 합세도 파이팅!

import javax.inject.Inject

class WavveDataSourceImpl @Inject constructor(
val wavveService: WavveService
Copy link
Contributor

Choose a reason for hiding this comment

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

private를 붙여줘도 좋을 듯,,!! 합니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저 맨날 이런 접근제어자를 제대로 활용못하는 것 같아요 ㅠ 좀 적용해봐야겠습니다

Comment on lines 5 to 6


Copy link
Contributor

Choose a reason for hiding this comment

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

요기는 2줄 공백이당 크크

Copy link
Contributor Author

Choose a reason for hiding this comment

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

어머나

import javax.inject.Inject

@HiltViewModel
class SignInViewModel @Inject constructor() : ViewModel() {
class SignInViewModel @Inject constructor(
private val user:User,
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

Choose a reason for hiding this comment

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

그리고 여기 user를 매개변수로 넣어주는 이유가 뭔가요? ㅜ 찾고 싶은데 졸려서 그런가 안 보여잉 ㅠㅠㅠ

import javax.inject.Singleton

@Singleton
class User @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

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

아 위에 있는 user가 이러한 역할을 하는 친구였군요!
승민님 리뷰처럼 조금 더 직관적으로 네이밍하는 게 필요할 것 같아요!
id랑 비밀번호 등을 저장하는 user data class인 줄 알았서요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

말씀주신대로 네이밍을 수정해보도록 할게요!

@hyeeum hyeeum merged commit fe53ce4 into develop Dec 4, 2024
@hyeeum hyeeum deleted the week4 branch December 4, 2024 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

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