Conversation
| class DataStoreManager(private val context: Context) { | ||
| private val gson = Gson() | ||
| private val PRODUCT_LIST_KEY = stringPreferencesKey("product_list") | ||
| private val WISHLIST_KEY = stringPreferencesKey("wishlist_key") |
| class PurchaseFragment : Fragment(R.layout.fragment_purchase) { | ||
|
|
||
| private lateinit var dataStoreManager: DataStoreManager | ||
|
|
||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| super.onViewCreated(view, savedInstanceState) | ||
| dataStoreManager = DataStoreManager(requireContext()) | ||
|
|
||
|
|
||
| val tabLayout = view.findViewById<TabLayout>(R.id.purchase_tab_layout) | ||
|
|
||
|
|
||
| if (tabLayout.tabCount < 4) { | ||
| tabLayout.addTab(tabLayout.newTab().setText("Sale")) | ||
| } | ||
|
|
||
| tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { | ||
| override fun onTabSelected(tab: TabLayout.Tab?) { | ||
|
|
||
| when (tab?.position) { | ||
| 0 -> { /* 전체 */ } | ||
| 1 -> { /* Tops & T-Shirts */ } | ||
| 2 -> { /* Shoes */ } | ||
| 3 -> { /* Sale */ } | ||
| } | ||
| } | ||
| override fun onTabUnselected(tab: TabLayout.Tab?) {} | ||
| override fun onTabReselected(tab: TabLayout.Tab?) {} | ||
| }) | ||
|
|
||
|
|
||
| val heartBtn = view.findViewById<ImageView>(R.id.iv_socks) | ||
|
|
||
| heartBtn.setOnClickListener { | ||
| val currentProduct = Product("Nike Everyday Plus", "$10", R.drawable.socks) | ||
| toggleWishlist(currentProduct) | ||
| } | ||
| } | ||
|
|
||
| private fun toggleWishlist(product: Product) { | ||
| viewLifecycleOwner.lifecycleScope.launch { | ||
| // 1. 현재 위시리스트 가져오기 | ||
| val currentWishlist = dataStoreManager.getWishlist().first().toMutableList() | ||
|
|
||
| // 2. 이미 있으면 제거, 없으면 추가 | ||
| val isExist = currentWishlist.any { it.name == product.name } | ||
| if (isExist) { | ||
| currentWishlist.removeAll { it.name == product.name } | ||
| Toast.makeText(context, "위시리스트에서 제거되었습니다.", Toast.LENGTH_SHORT).show() | ||
| } else { | ||
| currentWishlist.add(product) | ||
| Toast.makeText(context, "위시리스트에 추가되었습니다!", Toast.LENGTH_SHORT).show() | ||
| } | ||
|
|
||
| dataStoreManager.saveWishlist(currentWishlist) | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
아이템이 가로로 2개씩 뜨도록 만드는게 좋을 것 같아용
| <TextView | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:text="9월 4일 목요일" |
There was a problem hiding this comment.
android:text 항목들 strings에 옮기면 유지보수에 좋을 거 같아요 ! 과제 수행하시느라 수고 많으셨습니다~!!
kimdoyeon1234
left a comment
There was a problem hiding this comment.
TabLayout과 DataStore를 연동해서 실시간으로 하트 아이콘 상태를 바꾸는 로직을 아주 잘 구현하셨네요! 다만 현재 레이아웃이 하드코딩 방식이라 레몬 말씀대로 가로 2줄 배치를 구현하기엔 한계가 있어 보입니다!!
이번 기회에 RecyclerView를 도입해서 더 유연하고 효율적인 리스트 구조로 3주차 피드백 반영과 함께 리팩토링 해보는것도 좋을거 같습니다! 고생 많으셨습니다!
| <ScrollView | ||
| android:id="@+id/purchase_scroll_view" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent"> | ||
|
|
||
| <LinearLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:orientation="vertical" | ||
| android:padding="16dp"> | ||
|
|
||
| <FrameLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="300dp"> | ||
| <ImageView | ||
| android:id="@+id/iv_socks" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:src="@drawable/socks" | ||
| android:scaleType="centerInside" | ||
| android:background="#F9F9F9" /> | ||
| <ImageView | ||
| android:id="@+id/btn_heart1" | ||
| android:layout_width="40dp" | ||
| android:layout_height="40dp" | ||
| android:layout_gravity="bottom|end" | ||
| android:layout_margin="12dp" | ||
| android:src="@drawable/ic_heart_empty" | ||
| android:padding="8dp"/> | ||
| </FrameLayout> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" /> | ||
| <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" /> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" /> | ||
|
|
||
| <FrameLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="300dp"> | ||
| <ImageView | ||
| android:id="@+id/iv_socks2" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:src="@drawable/socks" | ||
| android:scaleType="centerInside" | ||
| android:background="#F9F9F9" /> | ||
| <ImageView | ||
| android:id="@+id/btn_heart2" | ||
| android:layout_width="40dp" | ||
| android:layout_height="40dp" | ||
| android:layout_gravity="bottom|end" | ||
| android:layout_margin="12dp" | ||
| android:src="@drawable/ic_heart_empty" | ||
| android:padding="8dp"/> | ||
| </FrameLayout> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" /> | ||
| <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned2" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" /> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" /> | ||
|
|
||
| <FrameLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="300dp"> | ||
| <ImageView | ||
| android:id="@+id/iv_socks3" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:src="@drawable/socks" | ||
| android:scaleType="centerInside" | ||
| android:background="#F9F9F9" /> | ||
| <ImageView | ||
| android:id="@+id/btn_heart3" | ||
| android:layout_width="40dp" | ||
| android:layout_height="40dp" | ||
| android:layout_gravity="bottom|end" | ||
| android:layout_margin="12dp" | ||
| android:src="@drawable/ic_heart_empty" | ||
| android:padding="8dp"/> | ||
| </FrameLayout> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" /> | ||
| <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned3" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" /> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" /> | ||
|
|
||
| <FrameLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="300dp"> | ||
| <ImageView | ||
| android:id="@+id/iv_socks4" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:src="@drawable/socks" | ||
| android:scaleType="centerInside" | ||
| android:background="#F9F9F9" /> | ||
| <ImageView | ||
| android:id="@+id/btn_heart4" | ||
| android:layout_width="40dp" | ||
| android:layout_height="40dp" | ||
| android:layout_gravity="bottom|end" | ||
| android:layout_margin="12dp" | ||
| android:src="@drawable/ic_heart_empty" | ||
| android:padding="8dp"/> | ||
| </FrameLayout> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Training Crew Socks" android:textColor="#888888" android:textSize="14sp" android:layout_marginTop="16dp" /> | ||
| <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Nike Everyday Plus Cushioned4" android:textColor="@color/black" android:textSize="22sp" android:textStyle="bold" android:layout_marginTop="4dp" /> | ||
| <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="$10" android:textColor="@color/black" android:textSize="14sp" android:layout_marginTop="8dp" android:layout_marginBottom="40dp" /> | ||
|
|
||
| </LinearLayout> | ||
| </ScrollView> |
There was a problem hiding this comment.
현재 ScrollView 안에 유사한 형태의 아이템 레이아웃(FrameLayout 및 TextView들)이 반복적으로 직접 선언되어 있습니다. 아이템이 늘어날 경우 코드 관리가 어려워지고 메모리 효율도 떨어질 수 있어요. RecyclerView와 GridLayoutManager(spanCount = 2)를 사용하면 레몬 의견처럼 가로 2줄 배치도 쉽게 구현할 수 있고, 코드도 훨씬 깔끔해질 것 같습니다!
|
|
||
| val tabLayout = view.findViewById<TabLayout>(R.id.purchase_tab_layout) | ||
| val scrollView = view.findViewById<ScrollView>(R.id.purchase_scroll_view) | ||
|
|
||
|
|
||
| val heartButtons = listOf<ImageView>( | ||
| view.findViewById(R.id.btn_heart1), | ||
| view.findViewById(R.id.btn_heart2), | ||
| view.findViewById(R.id.btn_heart3), | ||
| view.findViewById(R.id.btn_heart4) | ||
| ) |
There was a problem hiding this comment.
현재 heartButtons를 리스트로 만들기 위해 findViewById를 개별적으로 호출하고 있는데, 만약 아이템이 100개라면 관리가 불가능해집니다. RecyclerView를 도입해서 Adapter 내부에서 ViewBinding으로 처리하도록 개선하면 좋을거같습니다!
| private fun toggleWishlist(product: Product) { | ||
| viewLifecycleOwner.lifecycleScope.launch { | ||
| val currentWishlist = dataStoreManager.getWishlist().first().toMutableList() | ||
| val isExist = currentWishlist.any { it.name == product.name } | ||
|
|
||
| if (isExist) { | ||
| currentWishlist.removeAll { it.name == product.name } | ||
| Toast.makeText(context, "위시리스트 제거", Toast.LENGTH_SHORT).show() | ||
| } else { | ||
| currentWishlist.add(product) | ||
| Toast.makeText(context, "위시리스트 추가!", Toast.LENGTH_SHORT).show() | ||
| } | ||
| dataStoreManager.saveWishlist(currentWishlist) | ||
| } |
There was a problem hiding this comment.
Fragment 내부에서 dataStoreManager를 직접 호출해 위시리스트를 추가/삭제하는 로직을 수행하고 있습니다. 나중에 앱이 커지면 유지보수가 힘들 수 있으니, 데이터 처리는 ViewModel로 옮기고 Fragment는 관찰만 하는 구조로 하면은 좋을거 같습니다..
📌 PR 제목
4주차 미션
🔗 관련 이슈
Closes #이슈번호
✨ 변경 사항
🔍 테스트
📸 스크린샷 (선택)
🚨 추가 이슈