Conversation
|
이전 주차 (week4) 과제까지 이번에 한 번에 진행해서 머지가 안 된 상태라 이번에도 파일 트래킹이 다 섞여버려서 코드 리뷰 진행하시기가 어려우실 것 같아요..ㅠㅠ 대략적으로 data 패키지의 datalocal, dataremote, di 패키지, domain/repository 패키지의 UserRepository, usecase 패키지 하의 파일들 확인해주시면 될 것 같습니다..! |
chattymin
left a comment
There was a problem hiding this comment.
6주차 과제 수고하셨습니다!!
clean architecture에 대해서 한번 더 공부해보시고 리펙토링 진행해보시면 좋을 것 같아요!
새로운 기술을 배워서 쓰는 것도 좋지만, 기존의 기술을 잘 알고 사용하는게 정말정말 중요하다고 생각해요. 앱잼이 다가오고 있는데 앱잼 전에 지금까지 배운 것들을 한번 복습하는 시간을 가져본다면 앱잼때 정말 큰 도움이 될거에요 :)
| import org.sopt.and.data.dataremote.model.request.RequestCreateUserDto | ||
| import org.sopt.and.data.dataremote.model.request.RequestGetUserDto | ||
| import org.sopt.and.data.dataremote.model.response.ResponseCreateUserSuccessDto | ||
| import org.sopt.and.data.dataremote.model.response.ResponseGetUserDto | ||
| import org.sopt.and.data.dataremote.model.response.ResponseGetUserHobbyDto | ||
| import retrofit2.Response |
There was a problem hiding this comment.
domain인데 data layer에 의존성을 가지고 있어요.
그리고 retrofit에 대한 의존성도 가지고 있어요.
domain모듈은 kotlin/java에 대한 의존성만 가지고 있어야 하기 때문에 의존성 제거바랍니다!
There was a problem hiding this comment.
의존성 분리라는 원래 목적을 생각하면서 리팩토링해보니, 클린 아키텍처가 지향하는 구조에 대해서 조금 더 감이 잡히는 것 같아요. 리뷰 감사합니다!!
t1nm1ksun
left a comment
There was a problem hiding this comment.
6,7주차 내용 저는 상당히 어렵던데 고생많으셨습니다!
이제 곧 앱잼하실 텐데 화이팅입니다!!
| data class LoginScreen( | ||
| val emailText: String, | ||
| val userNameText: String, | ||
| val passwordText: String | ||
| ) |
There was a problem hiding this comment.
아래에 있는 로그인화면과 네이밍이 똑같아서 헷갈릴 수 있을거같아요!
그리고 만약 사용하는곳이나 다른 data class들이 많아진다면 따로 폴더를 생성해서 관리하는것도 좋습니다!
| ) : ViewModel() { | ||
|
|
||
| //로그인 요청을 서버로 보내기 위함 | ||
| private val userService by lazy { ServicePool.userService } |
There was a problem hiding this comment.
UseCase를 통해 서버통신을 만들어 두었으니 이제 해당 코드는 사용되지 않을 것 같아요!
| fun StringInputValidCheck(newString: String): Boolean { | ||
| var isValid = false | ||
| val inputStr : CharSequence = email | ||
| val pattern = Patterns.EMAIL_ADDRESS | ||
| val matcher = pattern.matcher(inputStr) | ||
| if(matcher.matches()){ | ||
| isValid = true | ||
| val inputStr : CharSequence = newString | ||
|
|
||
| if(inputStr.length >= 8){ | ||
| return isValid | ||
| } else { | ||
| return !isValid | ||
| } | ||
| return isValid | ||
| } | ||
|
|
||
| fun PasswordValidCheck(password: String): Boolean { | ||
| val pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#\$%^&*])|(?=.*[a-z])(?=.*\\d)(?=.*[!@#\$%^&*])|(?=.*[A-Z])(?=.*\\d)(?=.*[!@#\$%^&*]).{8,20}\$".toRegex() | ||
| val pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#\$%^&*])|(?=.*[a-z])(?=.*\\d)(?=.*[!@#\$%^&*])|(?=.*[A-Z])(?=.*\\d)(?=.*[!@#\$%^&*]).{1,8}\$".toRegex() | ||
| return password.matches(pattern) | ||
|
|
||
| } |
There was a problem hiding this comment.
해당 로직들은 비지니스 로직이라고 생각합니다!
어디에 위치하면 좋을까요?
There was a problem hiding this comment.
validateUserInputUseCase에 유효성 검증 로직을 옮겨서 domain 레이어로 분리했습니다!! 꼼꼼한 리뷰 감사합니다 🥲
| ) : ViewModel() { | ||
|
|
||
| //로그인 요청을 서버로 보내기 위함 | ||
| private val userService by lazy { ServicePool.userService } |
There was a problem hiding this comment.
DI를 사용하고 있는데 ServicePool을 직접 참조하는 것은 DI 원칙에 위배됩니다!
민석이 코멘트 처럼 사용하지 않는 코드니 제거 해도 좋을 것 같네요
| private val _loginResult = MutableStateFlow<Boolean?>(null) | ||
| val loginResult = _loginResult.asStateFlow() |
There was a problem hiding this comment.
| private val _loginResult = MutableStateFlow<Boolean?>(null) | |
| val loginResult = _loginResult.asStateFlow() | |
| sealed class LoginUiState { | |
| object Initial : LoginUiState() | |
| object Success : LoginUiState() | |
| data class Error(val message: String) : LoginUiState() | |
| } | |
| private val _loginState = MutableStateFlow<LoginUiState>(LoginUiState.Initial) | |
| val loginState = _loginState.asStateFlow() |
Boolean? 대신 sealed class를 사용하여 상태를 더 명확하게 표현할 수 있어요!
| _shouldShowPassword.value = !_shouldShowPassword.value | ||
| } | ||
|
|
||
| // 입력값이 조건에 맞는지 확인 (둘 다 여덟 자 이하?) |
There was a problem hiding this comment.
유효성 검사 로직을 별도의 UseCase나 다른 클래스로 분리하는게 좋아보여요
| _user.value.name = userName | ||
| _user.value.hobby = hobby | ||
| _user.value.accessToken = accessToken |
There was a problem hiding this comment.
| _user.value.name = userName | |
| _user.value.hobby = hobby | |
| _user.value.accessToken = accessToken | |
| _user.value = _user.value.copy( | |
| name = userName, | |
| hobby = hobby, | |
| accessToken = accessToken | |
| ) |
직접 필드 수정은 권장되지 않는걸로 알아서 요렇게 해보는건 어때요?
| private val postSignUpUseCase: PostSignUpUseCase | ||
| ) : ViewModel() { | ||
|
|
||
| private val userService by lazy { ServicePool.userService } |
There was a problem hiding this comment.
ServicePool을 통해 UserService를 가져오는 대신, 생성자를 통한 의존성 주입을 사용하는 것이 좋을 것 같습니다
private val userService by lazy { ServicePool.userService }
↓
@Inject constructor(
private val userService: UserService,
private val postSignUpUseCase: PostSignUpUseCase
)
| return _isUserNameValid.value && _isPasswordValid.value | ||
| } | ||
|
|
||
| suspend fun createNewUser() { |
There was a problem hiding this comment.
createNewUser() 함수가 너무 많은 일을 하고 있는것 같아요. 에러 처리 로직을 별도의 private 함수로 분리하면 코드 가독성이 더 좋아질 것 같습니다. 지금보다 훨씬 깔끔해질 거예요!
angryPodo
left a comment
There was a problem hiding this comment.
지원아 고생 많았다ㅠ
코틀린도 안 익숙한데 이 정도로 적용한 거 보면 진짜 대견하다! 아키텍처 패턴이랑 로직 분리는 나도 아직도 감이 잘 안 오는 부분이라... 앱잼 하면서 실전으로 부딪혀보면 왜 써야 하는지 더 와닿을 거고, 그 다음부터 자연스럽게 발전시켜나갈 수 있을 거야.마지막까지 힘내보자!
jihyunniiii
left a comment
There was a problem hiding this comment.
전체적인 의존성 방향이랑 네비게이션 관련한 내용들 세미나 자료 다시 읽어보시면서 리팩토링 해보면 좋을 것 같아요! 고생하셨습니다.
| import org.sopt.and.data.dataremote.model.response.ResponseGetUserHobbyDto | ||
| import org.sopt.and.domain.repository.UserRepository |
There was a problem hiding this comment.
domain 레이어인데 data에 대한 의존성을 가지고 있어요.
세미나 때 배운 의존성을 바탕으로 어떻게 수정하면 좋을지 고민해봅시다.
|
|
||
| import org.sopt.and.data.dataremote.model.response.ResponseGetUserHobbyDto | ||
| import org.sopt.and.domain.repository.UserRepository | ||
| import retrofit2.Response |
There was a problem hiding this comment.
마찬가지로 domain 레이어는 순수한 java, kotlin 코드로 이루어져야 합니다.
|
|
||
| var emailState = remember { mutableStateOf(inputEmail) } | ||
| var passwordState = remember { mutableStateOf(inputPassword) } | ||
| var isUserNameValid = loginViewModel.isUserNameValid.collectAsState().value |
There was a problem hiding this comment.
collectAsState와 collectAsStateWithLifecycle의 차이는 무엇일까요?
There was a problem hiding this comment.
collectAsStateWithLifecycle은 백그라운드 상태처럼 화면이 활성화되지 않았을 때는 굳이 State를 계속 관찰하고 있지 않아서 조금 더 효율적으로 상태를 관리할 수 있는 것으로 알고 있습니다!
매번 collectAsStateWithLifecycle을 사용하는 것이 무조건 좋지만은 않을 것 같아서, 구독하고 있는 StateFlow가 얼마나 자주 변경되는 값인지? 등을 고려해서 사용하는 법을 익히는 게 좋을 것 같습니다
| import org.sopt.and.data.datalocal.datasource.UserInfoLocalDataSource | ||
| import org.sopt.and.data.dataremote.network.ServicePool |
There was a problem hiding this comment.
여기도 의존성 다시 한 번 고민해봅시다.
There was a problem hiding this comment.
리뷰 반영해서 의존성에 대해 고민해보고 모든 Viewmodel에서 data 레이어 관련 의존성 제거했습니다! (UseCase -> UserRepositoryImpl으로 분리)
| navController.navigate(Route.SearchScreen) | ||
| } | ||
| "profile" -> { | ||
| navController.navigate(Route.MypageScreen(userName = "")) /*username을 여기다 어떻게 넣어주지?*/ |
There was a problem hiding this comment.
Route sealed class를 어떻게 활용하면 값을 넣어줄 수 있을지 생각해보세요!
Related issue 🛠
Work Description ✏️
Screenshot 📸
Uncompleted Tasks 😅
To Reviewers 📢