Skip to content

feat: 4주차 미션_제로#31

Open
jeongkyueun wants to merge 5 commits intomainfrom
zero-m4
Open

feat: 4주차 미션_제로#31
jeongkyueun wants to merge 5 commits intomainfrom
zero-m4

Conversation

@jeongkyueun
Copy link
Copy Markdown
Collaborator

@jeongkyueun jeongkyueun commented Apr 8, 2026

📌 [feat/#30] 4주차 미션_제로

DataStore와 Gson을 연동하여 앱 내 상품 데이터(위시리스트 상태)를 영구 저장하고, 구매하기 화면에 Tab Layout을 구현하여 5주차 과제 기반을 마련했습니다.

🔗 관련 이슈

Closes #30

✨ 변경 사항

  1. DataStore & Gson 연동 (DataStoreManager.kt)
  • 영구 저장 기능: Preference DataStore를 사용하여 상품 리스트를 JSON 문자열로 직렬화하여 저장하고, 앱을 재시작해도 하트(위시리스트) 상태가 유지되도록 구현했습니다.
  • 상태 업데이트: updateWishlistStatus 메서드를 통해 특정 상품의 하트 상태를 갱신합니다.
  • 초기 데이터: DataStore가 비어있을 때 기본 더미 데이터를 자동으로 생성하도록 초기화 로직을 추가했습니다.
  1. 화면별 DataStore 적용 및 UI 연동
  • WishlistFragment: DataStore의 데이터를 관찰하여 isWishlisted가 true인 상품만 필터링하여 리스트로 보여줍니다.
  • ProductDetailFragment: 하트 버튼 클릭 시 DataStore의 값을 업데이트하며, 실시간 데이터 변경(productsFlow)을 관찰하여 UI를 갱신합니다.
  1. 구매하기(Purchase) 페이지 탭 기능 추가
  • TabLayout + ViewPager2: 구매하기 화면 내에 탭 기능을 구현하여 컨텐츠를 분리했습니다.
  • 탭 구성:
  • All: 기존 전체 상품 리스트 (AllProductsFragment)
  • Top & T-shirts: 빈 화면 (TopsTshirtsFragment)
  • Sale: 빈 화면 (SaleFragment)

-> 5주차 과제 수행을 위해 필요한 빈 Fragment들을 생성하고 연결했습니다.

  1. 비동기 처리 및 DataStore로 더미 데이터 대체하기

    • 홈 화면 나이키 최신 상품, 구매하기, 위시리스트 전부 DataStore로 변경하기

      data class를 저장하기 위해선 Proto DataStore 방식도 존재하지만 저희는 json으로 변환하는 방식으로 함.

      // gson 추가
      implementation("com.google.code.gson:gson:2.10.1") 
      
      //객체 -> json
      private val gson = Gson()
      val jsonString = gson.toJson(Object) // object는 data class 이름
      
      //json -> 객체
      gson.fromJson(jsonString, Object::class.java)
    • 위시리스트 기능(하트 기능) 구현하기

      • 구매하기에서 하트 버튼 누르면 위시리스트에서 보여지기
      • 하트 버튼 누르면 유지(앱을 껐다 켜도 하트 버튼 활성화 및 장바구니 기록 유지됨)
  2. 구매하기 페이지에 Tab기능 추가하기

    Top & T-shirts, sale 의 Tab으로 빈 화면의 Fragment 생성

🔍 테스트

  • 테스트 완료
  • 에러 없음

📸 스크린샷 (선택)

구매하기
image image
위시리스트 상세페이지
image image

실행영상

umc_android_zero_4th_run_video.mp4

🚨 추가 이슈

@jeongkyueun jeongkyueun self-assigned this Apr 8, 2026
@jeongkyueun jeongkyueun changed the title [feat/#00] 4주차 미션_제로 [feat/#30] 4주차 미션_제로 Apr 8, 2026
@jeongkyueun jeongkyueun added the enhancement New feature or request label Apr 8, 2026
@kimdoyeon1234 kimdoyeon1234 changed the title [feat/#30] 4주차 미션_제로 feat: 4주차 미션_제로 Apr 8, 2026
},
onWishlistClick = { product, position ->
product.isWishlisted = !product.isWishlisted
// In wishlist fragment, usually removing from wishlist means removing from the list
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

@sua710 sua710 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 +102 to +105
<TextView
android:id="@+id/tv_detail_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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.

디테일하게 다 구현하신 것 같아서 너무 대단하요


<!-- 제품 상세 정보 -->
<string name="product_description">The Nike Everyday Plus Cushioned Socks bring comfort to your workout with extra cushioning under the heel and forefoot and a snug, supportive arch band. Sweat-wicking power and breathability up top help keep your feet dry and cool to help push you through that extra set.</string>
<string name="wishlist_btn_text">위시리스트</string>
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

@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.

이번 주차에 Navigation Component와 DataStore, ViewBinding까지 최신 스택을 골고루 활용해 완성도 높은 결과물을 만들어내신 점 정말 인상적이고 고생 많으셨습니다!

특히 ConstraintLayout의 ratio 속성을 활용한 UI 대응과 데이터 레이어를 분리한 설계가 아주 좋았는데, 다만 성능 최적화를 위해 repeatOnLifecycle 사용이나 어댑터 객체 재사용 같은 부분만 조금 더 보완한다면 좋은 코드가 될 것 같습니다

짧은 시간 안에 많은 기능을 안정적으로 구현하시느라 정말 수고하셨고, 고생하셨어요!

Comment on lines +32 to +51

private fun setupRecyclerView() {
viewLifecycleOwner.lifecycleScope.launch {
dataStoreManager.productsFlow.collect { products ->
val adapter = ProductAdapter(
products,
onItemClick = { product ->
navigateToDetail(product)
},
onWishlistClick = { product, _ ->
viewLifecycleOwner.lifecycleScope.launch {
dataStoreManager.updateWishlistStatus(product.id, !product.isWishlisted)
}
}
)

binding.rvAllProducts.layoutManager = GridLayoutManager(context, 2)
binding.rvAllProducts.adapter = adapter
}
}
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.

현재 lifecycleScope.launch를 통해 collect를 호출하고 있는데, 이 경우 프래그먼트가 STOPPED 상태일 때도 계속 수집이 발생합니다. repeatOnLifecycle을 사용해 안정성을 높여보는 것도 좋을거 같습니다!

Comment on lines +36 to +39
val adapter = ProductAdapter(
products,
onItemClick = { product ->
navigateToDetail(product)
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.

데이터가 업데이트될 때마다 val adapter = ProductAdapter(...)를 새로 생성하고 있습니다. 어댑터는 멤버 변수로 한 번만 선언하고, 리스트 데이터가 바뀔 때는 어댑터 내부에 public 메서드(예: updateList)를 만들어 데이터만 갈아끼워 주는 것이 성능상 더 좋을거 같습니다!

Comment on lines +53 to +55
val products = if (jsonString.isEmpty()) {
getInitialProducts().toMutableList()
} else {
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.

updateWishlistStatus 메서드 내에서 초기 제품 리스트를 가져오는 로직이 productsFlow와 중복됩니다! 이미 productsFlow에서 초기 데이터 처리를 하고 있으니, 이 메서드에서는 현재 저장된 데이터를 first()로 가져와서 업데이트만 하도록 단순화할 수 있을 것 같아요!

Comment on lines +29 to +31

val product = arguments?.getParcelable<Product>("product")

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.

Android 13(Tiramisu) 이상부터 getParcelable의 사용 방식이 변경되었습니다. 호환성을 위해 아래와 같이 버전 분기 처리를 해주면 더 안전한 코드가 될 것 같습니다

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.

[Feat] 4주차 미션_제로

5 participants