Skip to content

feat: 6주차 미션_우가#46

Open
sua710 wants to merge 8 commits into
mainfrom
wooga-m6
Open

feat: 6주차 미션_우가#46
sua710 wants to merge 8 commits into
mainfrom
wooga-m6

Conversation

@sua710
Copy link
Copy Markdown
Collaborator

@sua710 sua710 commented May 6, 2026

📌 PR 제목

MVVM 아키텍처 및 Hilt 의존성 주입 적용

🔗 관련 이슈

Closes #이슈번호

✨ 변경 사항

  • Hilt 설정 추가

    • NikeApplication 생성 및 @HiltAndroidApp 적용
    • MainActivity, 주요 Fragment에 @AndroidEntryPoint 적용
    • Hilt 의존성 및 Gradle 설정 추가
  • Repository 구조 분리

    • domain/repository에 Repository interface 추가
    • data/repository에 실제 구현체 분리
    • ProductRepositoryImpl, ProfileRepositoryImpl 구성
    • RepositoryModule을 통해 interface와 구현체 바인딩
  • 네트워크 의존성 주입 적용

    • NetworkModule 추가
    • OkHttpClient, Retrofit, ReqResService를 Hilt로 제공
  • ViewModel 구조 개선

    • ProductViewModel, ProfileViewModel@HiltViewModel 적용
    • ViewModel 내부에서 Repository/DataStore를 직접 생성하던 코드 제거
    • 생성자 주입 방식으로 변경
  • StateFlow 기반 UI 상태 관리 적용

    • 기존 LiveData observe 방식 일부를 StateFlow collect 방식으로 변경
    • repeatOnLifecycle(Lifecycle.State.STARTED)를 사용해 생명주기 안전하게 수집
  • Profile 관련 코드 정리

    • 기존 ProfileViewModelFactory 제거
    • ProfileUserUiModel 별도 파일로 분리
    • Profile API 호출 로직을 Repository 구현체로 이동

🔍 테스트

  • 테스트 완료
  • 에러 없음

📸 스크린샷 (선택)

🚨 추가 이슈

  • AGP 9.x와 Hilt/Kapt 호환 문제로 인해 gradle.properties에 임시 우회 설정을 추가함
    • android.builtInKotlin=false
    • android.newDsl=false
  • 해당 설정은 deprecated warning이 발생하므로, 추후 AGP/Hilt/KSP 조합으로 정리하는 것을 권장

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이전 주차 것들 빼도 될 것 같기도요?

@@ -1,4 +1,4 @@
package com.example.week2
package com.example.nike
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

파일 정리가 깔끔하시다!

Copy link
Copy Markdown
Collaborator

@jeongkyueun jeongkyueun 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
Copy Markdown
Collaborator

@kimdoyeon1234 kimdoyeon1234 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!

이번 코드는 패키지 구조를 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을 별도 패키지로 분리해서 의존 방향을 바로잡아주세요!

전반적으로 아키텍처 구조는 이번 스터디에서 가장 잘 잡으셨어요.
위 부분들만 다듬어주시면 완성도 높은 코드가 될 것 같습니다. 고생 많으셨어요! 😊

Comment on lines +21 to +38
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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NetworkModule에서 이미 Hilt로 Retrofit을 제공하고 있는데 ReqResApiClient object가 별도로 남아있어요! Hilt를 도입했다면 ReqResApiClient는 삭제하고 NetworkModule만 사용해주세요 :)

Comment on lines +12 to +23
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
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ProductUiModel이 data/repository/ 패키지 안에 선언되어 있어요! 저번 주차에도 언급했던거지만 혹시나 해서 다시 말해보자면, UiModel은 ui/ 패키지 혹은 별도의 model 패키지에 두는 게 더 자연스럽고,
domain/repository/ProductRepository.kt에서 data 패키지를 import하는 역방향 의존성이 생기게 됩니다!

Comment on lines +2 to +4

import com.example.nike.data.repository.ProductUiModel
import kotlinx.coroutines.flow.Flow
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

domain 레이어가 data 레이어를 import하고 있어요! 클린 아키텍처에서 domain은 data에 의존하면 안 됩니다! ProductUiModel을 domain/model/ 또는 ui/ 패키지로 옮겨서 의존 방향을 domain → data가 아닌 data → domain으로 맞춰주세요 :)

Comment on lines +42 to +43
viewModel.allProducts.collect { products ->
adapter.submitList(products.filter { it.isNew })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isNew 필터링 로직이 Fragment에 있어요! 이런 비즈니스 로직은 ViewModel에서 처리하고 Fragment는 결과만 받아서 표시하는 역할만 하도록 수정해봐도 좋을거 같습니다!

Comment on lines +42 to +43
viewModel.allProducts.collect { products ->
adapter.submitList(products.filter { it.isLiked })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

위시리스트 필터링 로직도 Fragment에 있습니다! HomeFragment와 동일하게 ViewModel에서 처리해주세요

Comment on lines +42 to +43
viewModel.allProducts.collect { products ->
adapter.submitList(products.filter { it.category == CATEGORY_SALE })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

카테고리 필터링도 Fragment가 아닌 ViewModel에서 처리해주세요! 각 화면에 맞는 전용 StateFlow를 ViewModel에 만들어두면 더 깔끔해요 ! SaleFragment.kt, TopFragment.kt 두개 다!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

별건 아니지만 MainActivity도 ui/main/ 패키지로 이동시키면은 통일감이 있을거 같습니다!

Comment on lines +79 to +85
Glide.with(this)
.load(profile.avatarUrl)
.placeholder(R.drawable.profile_avatar_placeholder)
.error(R.drawable.profile_avatar_placeholder)
.circleCrop()
.into(binding.ivProfileAvatar)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이미지 로딩을 BindingAdapter로 분리하면 Fragment 코드가 더 깔끔해져요! ProfileBindingAdapters.kt !

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.

5 participants