Conversation
| @@ -1,4 +1,4 @@ | |||
| package com.example.week2 | |||
| package com.example.nike | |||
jeongkyueun
left a comment
There was a problem hiding this comment.
폴더 구조가 깔끔하게 잘 정리되어 있어요!! 과제도 잘 수행 하셨습니다!
kimdoyeon1234
left a comment
There was a problem hiding this comment.
수고하셨습니다!
이번 코드는 패키지 구조를 data/local, data/remote, data/repository,
domain/repository, di/, ui/ 로 정말 깔끔하게 잘 나눠주셨고,
RepositoryModule에서 interface와 구현체를 @BINDS로 분리한 점,
ProfileUiState를 sealed interface로 Loading/Success/Error 상태를
명확하게 구분한 점, 모든 Adapter에 DiffUtil을 적용한 점이 정말 훌륭했어요!
다만 몇 가지 수정하면 좋을점이 있어 정리를 해보자면,
NetworkModule이 있는데 ReqResApiClient object가 아직 남아있으니 삭제해주시고,
HomeFragment, WishlistFragment, SaleFragment, TopFragment에서
products.filter { } 같은 비즈니스 로직이 Fragment 안에 있는데
이 부분은 ViewModel에서 처리하고 Fragment는 결과만 받아서 보여주도록 수정해주세요 :)
또한 ProductUiModel이 data/repository/ 안에 선언되어 있어서
domain 레이어가 data 레이어를 import하는 역방향 의존성이 생기고 있어요.
ProductUiModel을 별도 패키지로 분리해서 의존 방향을 바로잡아주세요!
전반적으로 아키텍처 구조는 이번 스터디에서 가장 잘 잡으셨어요.
위 부분들만 다듬어주시면 완성도 높은 코드가 될 것 같습니다. 고생 많으셨어요! 😊
| private val okHttpClient = OkHttpClient.Builder() | ||
| .addInterceptor(authenticationInterceptor) | ||
| .apply { | ||
| if (BuildConfig.DEBUG) { | ||
| addInterceptor(loggingInterceptor) | ||
| } | ||
| } | ||
| .connectTimeout(30, TimeUnit.SECONDS) | ||
| .readTimeout(30, TimeUnit.SECONDS) | ||
| .writeTimeout(30, TimeUnit.SECONDS) | ||
| .build() | ||
|
|
||
| val service: ReqResService = Retrofit.Builder() | ||
| .baseUrl(BASE_URL) | ||
| .client(okHttpClient) | ||
| .addConverterFactory(GsonConverterFactory.create()) | ||
| .build() | ||
| .create(ReqResService::class.java) |
There was a problem hiding this comment.
NetworkModule에서 이미 Hilt로 Retrofit을 제공하고 있는데 ReqResApiClient object가 별도로 남아있어요! Hilt를 도입했다면 ReqResApiClient는 삭제하고 NetworkModule만 사용해주세요 :)
| data class ProductUiModel( | ||
| val id: Int, | ||
| val name: String, | ||
| val description: String = "", | ||
| val price: Int, | ||
| val imageResId: Int, | ||
| val colorCount: Int = 0, | ||
| val isBestSeller: Boolean = false, | ||
| var isLiked: Boolean, | ||
| val category: String, | ||
| val isNew: Boolean | ||
| ) |
There was a problem hiding this comment.
ProductUiModel이 data/repository/ 패키지 안에 선언되어 있어요! 저번 주차에도 언급했던거지만 혹시나 해서 다시 말해보자면, UiModel은 ui/ 패키지 혹은 별도의 model 패키지에 두는 게 더 자연스럽고,
domain/repository/ProductRepository.kt에서 data 패키지를 import하는 역방향 의존성이 생기게 됩니다!
|
|
||
| import com.example.nike.data.repository.ProductUiModel | ||
| import kotlinx.coroutines.flow.Flow |
There was a problem hiding this comment.
domain 레이어가 data 레이어를 import하고 있어요! 클린 아키텍처에서 domain은 data에 의존하면 안 됩니다! ProductUiModel을 domain/model/ 또는 ui/ 패키지로 옮겨서 의존 방향을 domain → data가 아닌 data → domain으로 맞춰주세요 :)
| viewModel.allProducts.collect { products -> | ||
| adapter.submitList(products.filter { it.isNew }) |
There was a problem hiding this comment.
isNew 필터링 로직이 Fragment에 있어요! 이런 비즈니스 로직은 ViewModel에서 처리하고 Fragment는 결과만 받아서 표시하는 역할만 하도록 수정해봐도 좋을거 같습니다!
| viewModel.allProducts.collect { products -> | ||
| adapter.submitList(products.filter { it.isLiked }) |
There was a problem hiding this comment.
위시리스트 필터링 로직도 Fragment에 있습니다! HomeFragment와 동일하게 ViewModel에서 처리해주세요
| viewModel.allProducts.collect { products -> | ||
| adapter.submitList(products.filter { it.category == CATEGORY_SALE }) |
There was a problem hiding this comment.
카테고리 필터링도 Fragment가 아닌 ViewModel에서 처리해주세요! 각 화면에 맞는 전용 StateFlow를 ViewModel에 만들어두면 더 깔끔해요 ! SaleFragment.kt, TopFragment.kt 두개 다!
There was a problem hiding this comment.
별건 아니지만 MainActivity도 ui/main/ 패키지로 이동시키면은 통일감이 있을거 같습니다!
| Glide.with(this) | ||
| .load(profile.avatarUrl) | ||
| .placeholder(R.drawable.profile_avatar_placeholder) | ||
| .error(R.drawable.profile_avatar_placeholder) | ||
| .circleCrop() | ||
| .into(binding.ivProfileAvatar) | ||
| } |
There was a problem hiding this comment.
이미지 로딩을 BindingAdapter로 분리하면 Fragment 코드가 더 깔끔해져요! ProfileBindingAdapters.kt !
📌 PR 제목
MVVM 아키텍처 및 Hilt 의존성 주입 적용
🔗 관련 이슈
Closes #이슈번호
✨ 변경 사항
Hilt 설정 추가
NikeApplication생성 및@HiltAndroidApp적용MainActivity, 주요 Fragment에@AndroidEntryPoint적용Repository 구조 분리
domain/repository에 Repository interface 추가data/repository에 실제 구현체 분리ProductRepositoryImpl,ProfileRepositoryImpl구성RepositoryModule을 통해 interface와 구현체 바인딩네트워크 의존성 주입 적용
NetworkModule추가OkHttpClient,Retrofit,ReqResService를 Hilt로 제공ViewModel 구조 개선
ProductViewModel,ProfileViewModel에@HiltViewModel적용StateFlow 기반 UI 상태 관리 적용
LiveData observe방식 일부를StateFlow collect방식으로 변경repeatOnLifecycle(Lifecycle.State.STARTED)를 사용해 생명주기 안전하게 수집Profile 관련 코드 정리
ProfileViewModelFactory제거ProfileUserUiModel별도 파일로 분리🔍 테스트
📸 스크린샷 (선택)
🚨 추가 이슈
gradle.properties에 임시 우회 설정을 추가함android.builtInKotlin=falseandroid.newDsl=false