Skip to content

[쇼핑 주문 1, 2 단계] 모찌 미션 제출합니다 #111

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

Merged
merged 72 commits into from
Jun 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
cd91a8d
feat: set up the project
ijh1298 May 27, 2025
42ba4db
docs: 기능 요구 사항 작성
wondroid-world May 27, 2025
2f1c5da
build: shimmer 의존성 추가
wondroid-world May 27, 2025
c491ab8
feat: 상품 목록 shimmer layout 구현
wondroid-world May 27, 2025
12887e0
feat: recyclerView Item에 상품 목록 shimmer 추가
wondroid-world May 27, 2025
4d19a69
feat: recyclerView LoadingStateProduct의 ViewHolder, Adapter 적용
ijh1298 May 27, 2025
e7533ee
feat: 카탈로그 화면에 Shimmer UI 추가
ijh1298 May 27, 2025
2bcc720
feat: 장바구니 화면 로딩 UI 추가
ijh1298 May 27, 2025
f828b60
feat: 사용자 인증 정보 저장
wondroid-world May 27, 2025
d474112
build: retrofit 의존성 추가
wondroid-world May 27, 2025
abc8f6c
feat: 장바구니 아이템 DTO 추가
wondroid-world May 27, 2025
632b3c1
feat: Product service, Product retrofit 구현
wondroid-world May 27, 2025
f870e90
feat: RemoteCatalogProductRepositoryImpl 구현 및 적용
ijh1298 May 27, 2025
9b5bb82
feat: Retrofit 서비스 싱글톤으로 변경 및 로깅 인터셉터 추가
ijh1298 May 27, 2025
521cccd
refactor: 잘못 사용한 DTO import 변경
ijh1298 May 27, 2025
d2875b5
feat: 서버에서 상품 목록 불러오는 기능 구현
wondroid-world May 28, 2025
c28ff41
feat: 서버에서 상세 페이지 조회 구현
wondroid-world May 28, 2025
fe6ba66
feat: detailActivty에서 최근 본 상품 조회 구현
wondroid-world May 28, 2025
47e8031
feat: CartItem service, retrofit 구현
wondroid-world May 28, 2025
2ce319a
refactor: CartProductRepository를 Remote로 변경
ijh1298 May 28, 2025
bf436d4
feat: 장바구니에 상품 추가 후, cartItemId 콜백 받도록 구현
wondroid-world May 28, 2025
c2952ba
feat: 장바구니에 담긴 상품 수량 표시 및 상품 상세 화면으로 이동 시 상품 정보 전달 기능 개선
ijh1298 May 28, 2025
70bb188
feat: 제품 상세 페이지에서 장바구니에 없는 상품을 추가하면, 서버에 연동되는 로직 구현
wondroid-world May 28, 2025
220a27f
feat: 상품 목록 페이지 수량 증가 감소 버튼 로직 구현
wondroid-world May 28, 2025
89fff2f
fix: 상품 수량 1개일 때 장바구니에서 상품 삭제되도록 수정
ijh1298 May 28, 2025
879e6c2
feat: 상품 수량이 0인 경우 카트에 상품 추가, 0이 아닌 경우 수량 변경 기능 추가
ijh1298 May 28, 2025
ee1a4ab
refactor: 불필요한 레포지토리 및 객체 코드 제거
ijh1298 May 28, 2025
4a0656d
refactor: 불필요한 레포지토리 및 객체 코드 제거
ijh1298 May 28, 2025
9571d82
Merge branch 'step1' of https://github.com/ijh1298/android-shopping-o…
ijh1298 May 28, 2025
062b45c
docs: 2단계 기능 요구 사항 작성
wondroid-world May 29, 2025
f3cc100
feat: 주문 화면 레이아웃 구현
wondroid-world May 29, 2025
01c149e
feat: 장바구니 상품 추천 fragment layout 구현
wondroid-world May 29, 2025
0eaf9d4
feat: cart selection fragment 추가
wondroid-world May 29, 2025
e45ece4
fix: CartSelectionFragment 뷰 바인딩 오류 수정 및 레이아웃 조정
ijh1298 May 29, 2025
91e1b54
feat: 장바구니 상품 개별 선택 기능 추가
ijh1298 May 29, 2025
2069315
feat: cartActity에 cartViewModel 추가
wondroid-world May 29, 2025
ca132f9
feat: 장바구니 전체 금액, 총 개수 표시 기능 추가
ijh1298 May 29, 2025
a982974
fix: 장바구니 상품 선택 기능 및 총 금액 계산 기능 수정
ijh1298 May 29, 2025
4938217
feat: 장바구니에 담긴 상품이 없을 때 추천 상품 화면 표시 기능 추가
ijh1298 May 29, 2025
f2ca80f
feat: 추천상품 받아올 수 있는 로직 구현
wondroid-world May 29, 2025
43abf6a
feat: 장바구니 추천 상품 UI 적용
ijh1298 May 29, 2025
0f46e95
fix: 카트에 상품 목록이 없을 때, 상품 추천이 뜨도록 추천
wondroid-world May 29, 2025
80c2696
docs: 코니 요구 사항 일부 반영
wondroid-world Jun 2, 2025
8984d08
refactor: ShoppingApplication에 thread { DevMockServer.start() }와 사용하지…
wondroid-world Jun 3, 2025
e634d11
refactor: 장바구니 추천 상품에서 리사이클러뷰 4방향 다 제약 조건 설정
wondroid-world Jun 4, 2025
895a357
feat: local.properties에 아이디와 비밀번호를 저장하도록 로직 수정
wondroid-world Jun 4, 2025
47674c5
fix: 최근 상품 목록이 0개인 경우, 최근 본 상품 목록 뷰가 보이지 않게 하기
wondroid-world Jun 4, 2025
9e25d5a
refactor: 하드 코딩된 값이 아닌, BuildConfig에서 아이디와 비밀번호 불러오도록 로직 수정
wondroid-world Jun 4, 2025
0136aa4
refactor: CatalogViewModel에서 catalogProduct, quantity 사용하지 않는 값 삭제
wondroid-world Jun 4, 2025
4dcda41
refactor: CatalogViewModel에서 interface에서 remoteCatalogProductReposito…
wondroid-world Jun 4, 2025
29bb4c7
refactor: baseUrl 노출하지 않도록 수정
wondroid-world Jun 4, 2025
94d58dd
fix: 장바구니 개수 받아오는 api의 HTTP method 변경
wondroid-world Jun 4, 2025
e5ef783
refactor: cartItemCount를 관찰하여, fragment 변경되도록 로직 변경
wondroid-world Jun 4, 2025
bf2ff7a
refactor: cartItemCount 초기값 상수화
wondroid-world Jun 4, 2025
858d04d
refactor: activity 함수 분리
wondroid-world Jun 4, 2025
06d05b7
refactor: CartRecommendationFragment에서 사용하지 않는 함수와 Log 삭제
wondroid-world Jun 4, 2025
89deb67
refactor: 추천 리스트 adapter에서 사용하지 않는 함수는 override 하지 않도록 interface에서 U…
wondroid-world Jun 4, 2025
2db3e40
refactor: log 삭제
wondroid-world Jun 4, 2025
ef9a4d4
feat: 상품 목록에서 장바구니에 제품을 담으면, 장바구니 아이템 개수를 앱바에 노출
wondroid-world Jun 4, 2025
e373288
feat: 장바구니 추천상품에서 상품을 담을 수 있음
wondroid-world Jun 6, 2025
623e989
fix: 상품의 개수에 따라 전체 가격 변동 하도록 수정
wondroid-world Jun 6, 2025
986e49b
chore: 폴더 구조 변경
wondroid-world Jun 6, 2025
c5af081
fix: 장바구니 페이지에서 추천 상품을 장바구니에 담은 개수가 1개 초과했을 때, 발생하는 문제 해결
wondroid-world Jun 6, 2025
551aa90
refactor: 상수화
wondroid-world Jun 6, 2025
61626e6
refactor: Repository와 DataSource로 분리
wondroid-world Jun 6, 2025
6b256ca
refactor: 상수화
wondroid-world Jun 6, 2025
497488f
feat: 장바구니 상품 체크 박스 구현
wondroid-world Jun 6, 2025
ffb3a56
refactor: badge_background.xml에서 하드코딩된 색상값을 컬러 리소스로 변경
wondroid-world Jun 6, 2025
633608a
refactor: fragment에서 View가 inflate된 후, observeData를 구독하도록 수정
wondroid-world Jun 6, 2025
43a47af
refactor: 주석 삭제
wondroid-world Jun 6, 2025
e7407a5
refactor: Ktlint 적용
wondroid-world Jun 6, 2025
3e24363
docs: Readme 업데이트
wondroid-world Jun 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
# android-shopping-order
# android-shopping-order

### 1,2단계 기능 요구 사항
- 스켈레톤 UI 노출
- [x] 상품 목록
- [x] 장바구니
- 서버 연동
- [x] 장바구니 아이템
- [ ] 주문
- [x] 상품
- [x] 사용자 인증 정보 저장
- [x] 장바구니 화면에서 특정 상품만 골라 주문하기 버튼을 누를 수 있다.
- [x] 별도의 화면에서 상품 추천 알고리즘으로 사용자에게 적절한 상품을 추천해준다. (쿠팡 UX 참고)
- [x] 상품 추천 알고리즘은 최근 본 상품 카테고리를 기반으로 최대 10개 노출한다.
- [x] 예를 들어 가장 최근에 본 상품이 fashion 카테고리라면, fashion 상품 10개 노출
- [x] 해당 카테고리 상품이 10개 미만이라면 해당하는 개수만큼만 노출
- [x] 장바구니에 이미 추가된 상품이라면 미노출
- [x] 추천된 상품을 해당 화면에서 바로 추가하여 같이 주문할 수 있다.

### 코니 요구사항
- [x] 리드미 작성, PR 작성 잘하기

상품 목록 화면 구현
- [x] 최근 본 상품이 0개인 경우, 최근 본 상품 목록 삭제

Cart 화면 구현
- [x] 추천된 상품을 해당 화면에서 바로 추가하여 같이 주문할 수 있다. 구현
- [x] 총 가격은 변하는데 왜 체크 박스는 변하지 않는 기능 수정

- [x] ShoppingApplication에 thread { DevMockServer.start() } 삭제
- [x] ShoppingApplication에서 정말 SharedPreferences에 값을 저장하고 있는 지 확인 + SharedPreference에서 어떻게 동작하는 지 확인
- [x] CatalogViewModel에서 catalogProduct, quantity 사용하지 않는 값 삭제
- [x] CatalogViewModel에서 interface에서 remoteCatalogProductRepositoryImpl 실제 구현체 주입 -> 수정
- [x] 최근 상품 목록이 0개인 경우, 최근 본 상품 목록 뷰가 보이지 않게 하기
- [x] baseUrl 노출하지 않도록 수정
- [x] 사용자 정보 local.properties에 저장 -> BuildConfig로 갖고 오게 끔 구현
- [ ] 레벨업 부분 ) 인증에 필요한 key은 secerets에 저장
- CartActivity에서 생각해야 할 부분
- [x] CartActivity에서 hasHandledTotalCount가 정말 필요한 로직일지 생각
- [x] View에서 totalCount가 몇인지에 따라서 어떤한 fragment를 commit할지 아닌 ViewModel에서 어떠한 상태를 두고 그 상태에 따라서 화면을 이동하는 것
- [x] View까지 와서 단순 ViewModel의 함수를 호출하는 observe를 해야하는 지 생각 -> ViewModel의 일은 스스로 하게 변
- CartRecommendationFragment
- [x] 로그 삭제
- [x] 이런 식으로 Unit으로 처리를 하게 될 함수가 필수 overide 라면 ProductActionListener 에서 저 함수가 필수가 아니었던 게 아닌지 생각
- [x] collect는 viewLifecycleOwner 따라가게끔 해주셨는데, 데이터 바인딩에 사용될 lifecycleOwner 는 this 를 넘기고 있음
- CartSelectionFragment
- [x] 아직 View가 만들어지기 전에 view에 필요한 데이터를 미리 observe를 하고 adpater를 세팅할 필요한지
- [x] onCreateView에서는 어떠한 동작을 해야 하고 어떠한 단계인지 생각
- [x] ViewModelStoreOwner를 Activity로 잡은 점
- [x] Application이 왜 필요로 하는가?
- [x] Enabled 된 것이랑 updateCartItems 를 호출하는 상관관계 -> 전반적으로 view까지 observe가 되어야 했던 값들일까 하는 의문
- [x] 데이터 바인딩 적용
- [x] updateCartItems()가 view에서 처리해야하는 지 생각
- CartViewModel
- [x] totalCount가 -1 라는 것은 논리적으로 읽을 수 없음 -> 상수화
----- https://github.com/woowacourse/android-shopping-order/pull/111#discussion_r2113932605 여기 부터 적용 다시
36 changes: 35 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.android.junit5)
alias(libs.plugins.kotlin.android)
id("kotlin-kapt")
id("kotlin-parcelize")
}

android {
Expand All @@ -16,7 +20,18 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["runnerBuilder"] = "de.mannodermaus.junit5.AndroidJUnit5Builder"
testInstrumentationRunnerArguments["runnerBuilder"] =
"de.mannodermaus.junit5.AndroidJUnit5Builder"

val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localProperties.load(localPropertiesFile.inputStream())
}

buildConfigField("String", "USER_ID", "\"${localProperties.getProperty("USER_ID")}\"")
buildConfigField("String", "USER_PASSWORD", "\"${localProperties.getProperty("USER_PASSWORD")}\"")
buildConfigField("String", "BASE_URL", "\"${localProperties.getProperty("BASE_URL")}\"")
}

buildTypes {
Expand All @@ -41,17 +56,36 @@ android {
excludes += "win32-x86*/**"
}
}
buildFeatures {
dataBinding = true
buildConfig = true
}
}

dependencies {
implementation(libs.glide)
implementation(libs.androidx.room.runtime)
kapt(libs.androidx.room.compiler)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core.ktx)
implementation(libs.google.material)
implementation(libs.androidx.activity)
implementation(libs.okhttp)
implementation(libs.gson)
implementation(libs.mockwebserver)
implementation(libs.shimmer)
implementation(libs.retrofit)
implementation(libs.converter.gson)
implementation(libs.logging.interceptor)
implementation(libs.androidx.fragment.ktx)

testImplementation(libs.androidx.core.testing)
testImplementation(libs.assertj.core)
testImplementation(libs.junit.jupiter)
testImplementation(libs.kotest.runner.junit5)

androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.runner)
Expand Down
15 changes: 13 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:name=".ShoppingApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -13,7 +18,13 @@
android:theme="@style/Theme.Shopping"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".cart.CartActivity"
android:exported="false" />
<activity
android:name=".product.detail.DetailActivity"
android:exported="false" />
<activity
android:name=".product.catalog.CatalogActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -23,4 +34,4 @@
</activity>
</application>

</manifest>
</manifest>
20 changes: 0 additions & 20 deletions app/src/main/java/woowacourse/shopping/MainActivity.kt

This file was deleted.

14 changes: 14 additions & 0 deletions app/src/main/java/woowacourse/shopping/ShoppingApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package woowacourse.shopping

import android.app.Application
import woowacourse.shopping.data.database.ShoppingDatabase

class ShoppingApplication : Application() {
lateinit var database: ShoppingDatabase
private set

override fun onCreate() {
super.onCreate()
database = ShoppingDatabase.getInstance(applicationContext)
}
}
79 changes: 79 additions & 0 deletions app/src/main/java/woowacourse/shopping/cart/CartActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package woowacourse.shopping.cart

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import woowacourse.shopping.R
import woowacourse.shopping.cart.recoomendation.RecommendationFragment
import woowacourse.shopping.cart.selection.SelectionFragment
import woowacourse.shopping.databinding.ActivityCartBinding

class CartActivity : AppCompatActivity() {
private val binding: ActivityCartBinding by lazy {
DataBindingUtil.setContentView(this, R.layout.activity_cart)
}
private val viewModel: CartViewModel by lazy {
ViewModelProvider(
this,
CartViewModelFactory(),
)[CartViewModel::class.java]
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

initView()
initActionBar()
initDataBinding()
observeData()
}

private fun initView() {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}

private fun initActionBar() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = getString(R.string.text_cart_action_bar)
}

override fun onSupportNavigateUp(): Boolean {
finish()
return super.onSupportNavigateUp()
}

private fun initDataBinding() {
binding.vm = viewModel
binding.lifecycleOwner = this
}

private fun observeData() {
viewModel.totalPurchaseCount.observe(this) { totalPurchaseCount ->
val fragment =
when (totalPurchaseCount) {
0 -> RecommendationFragment()
else -> SelectionFragment()
}
supportFragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.fragment_container_cart_selection, fragment)
}
}
}

companion object {
fun newIntent(context: Context): Intent = Intent(context, CartActivity::class.java)
}
}
64 changes: 64 additions & 0 deletions app/src/main/java/woowacourse/shopping/cart/CartAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package woowacourse.shopping.cart

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import woowacourse.shopping.cart.CartItem.PaginationButtonItem
import woowacourse.shopping.cart.CartItem.ProductItem
import woowacourse.shopping.product.catalog.ProductUiModel
import woowacourse.shopping.product.catalog.QuantityControlListener

class CartAdapter(
cartItems: List<CartItem>,
private val onDeleteProductClick: DeleteProductClickListener,
private val onPaginationButtonClick: PaginationButtonClickListener,
private val quantityControlListener: QuantityControlListener,
private val onCheckClick: CheckClickListener,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val cartItems: MutableList<CartItem> = cartItems.toMutableList()

fun setCartItems(cartProducts: List<CartItem>) {
notifyItemRangeRemoved(0, cartItems.size)
cartItems.clear()
cartItems.addAll(cartProducts)
notifyItemRangeInserted(0, cartItems.size)
}

fun setCartItem(product: ProductUiModel) {
val index = cartItems.filterIsInstance<ProductItem>().indexOfFirst { it.productItem.id == product.id }
cartItems[index] = ProductItem(product)
notifyItemChanged(index)
}

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): RecyclerView.ViewHolder =
if (viewType == CART_PRODUCT) {
CartViewHolder.from(parent, onDeleteProductClick, quantityControlListener, onCheckClick)
} else {
PaginationButtonViewHolder.from(parent, onPaginationButtonClick)
}

override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
) {
when (holder) {
is CartViewHolder -> holder.bind((cartItems[position] as ProductItem).productItem)
is PaginationButtonViewHolder -> holder.bind(cartItems[position] as PaginationButtonItem)
}
}

override fun getItemViewType(position: Int): Int =
when (cartItems[position]) {
is PaginationButtonItem -> PAGINATION_BUTTON
is ProductItem -> CART_PRODUCT
}

override fun getItemCount(): Int = cartItems.size

companion object {
private const val CART_PRODUCT = 1
private const val PAGINATION_BUTTON = 2
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/woowacourse/shopping/cart/CartItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package woowacourse.shopping.cart

import woowacourse.shopping.product.catalog.ProductUiModel

sealed class CartItem {
data class ProductItem(
val productItem: ProductUiModel,
) : CartItem()

data class PaginationButtonItem(
val page: Int,
val isNextButtonEnabled: Boolean,
val isPrevButtonEnabled: Boolean,
) : CartItem()
}
34 changes: 34 additions & 0 deletions app/src/main/java/woowacourse/shopping/cart/CartViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package woowacourse.shopping.cart

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import woowacourse.shopping.databinding.CartItemBinding
import woowacourse.shopping.product.catalog.ProductUiModel
import woowacourse.shopping.product.catalog.QuantityControlListener

class CartViewHolder(
private val binding: CartItemBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(cartProduct: ProductUiModel) {
binding.cartProduct = cartProduct
binding.checkboxSelection.isChecked = cartProduct.isChecked
}

companion object {
fun from(
parent: ViewGroup,
onDeleteProductClick: DeleteProductClickListener,
quantityControlListener: QuantityControlListener,
onCheckClick: CheckClickListener,
): CartViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = CartItemBinding.inflate(inflater, parent, false)
binding.clickListener = onDeleteProductClick
binding.checkClickListener = onCheckClick
binding.layoutQuantityControlBar.quantityControlListener = quantityControlListener

return CartViewHolder(binding)
}
}
Comment on lines +18 to +33
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

factory method의 책임 분배에 대해 생각해보세요

companion object의 from 메서드에서 많은 일들을 처리하고 있네요!

다음 질문들을 통해 설계를 다시 한번 검토해보시면 어떨까요:

  1. 현재 4개의 서로 다른 listener를 설정하고 있는데, 이것이 모두 factory method의 책임일까요?
  2. 만약 이 ViewHolder를 단위 테스트하고 싶다면 어떤 부분이 어려울까요?
  3. binding 설정 로직을 별도로 분리하면 어떤 장점이 있을까요?

코드의 책임을 어떻게 나누면 더 테스트하기 쉽고 유지보수하기 좋을지 고민해보세요!

🤖 Prompt for AI Agents
In app/src/main/java/woowacourse/shopping/cart/CartViewHolder.kt around lines 18
to 33, the from factory method is handling multiple responsibilities by setting
four different listeners directly. To improve design, extract the listener
binding logic into a separate method or class responsible for configuring the
binding. This separation will make the factory method simpler, enhance
testability by isolating listener setup, and improve maintainability by clearly
dividing responsibilities.

}
Loading