Skip to content
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

[부나] 2단계 재화 미션 제출합니다. #26

Merged
merged 78 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a7a3ad9
feat: 하마드 서버 base url 추가
tmdgh1592 May 26, 2023
b100514
feat(Page): 공통 코드를 Page 클래스로 이동
tmdgh1592 May 29, 2023
79a07c9
docs: 주문 기능 구현 기능 목록 작성
tmdgh1592 May 30, 2023
8b7cc29
chore: Retrofit 라이브러리 추가
tmdgh1592 May 30, 2023
4e05fce
feat: RetrofitServiceCreater 구현
tmdgh1592 May 30, 2023
ffbf8c3
refactor: OkHttpClient를 Retrofit으로 리팩터링
tmdgh1592 May 30, 2023
708d742
feat: 상품 주문하기 ui 구현
tmdgh1592 May 30, 2023
9ad002d
feat: 주문할 상품을 보여주는 화면 구현
tmdgh1592 May 31, 2023
491ff6a
feat: PointEditText 커스텀뷰 구현
tmdgh1592 May 31, 2023
b2de7d3
feat: 장바구니 아이템 주문하는 기능 구현
tmdgh1592 May 31, 2023
902b7c9
feat: 쇼핑 목록 화면으로 이동하는 기능 구현
tmdgh1592 May 31, 2023
4f6292c
feat: 주문 목록을 불러오는 기능 구현
tmdgh1592 May 31, 2023
521ed80
feat: 상세 주문 내역을 보여주는 기능 구현
tmdgh1592 Jun 1, 2023
db6e20d
fix: 상품 주문시, 상품 id와 수량을 서버에 보내는 대신 cartItemId를 전달하도록 변경
tmdgh1592 Jun 1, 2023
cc52965
refactor: RecyclerViewBindingAdapter의 setAdapter 메서드의 인자를 ConcatAdapt…
tmdgh1592 Jun 1, 2023
9b4c856
refactor: ORDER_DETAIL과 ORDER 뷰타입의 value를 OrderProduct에서 관리하도록 변경
tmdgh1592 Jun 1, 2023
c9babbe
refactor: cartItemIds를 orderItems로 변경
tmdgh1592 Jun 1, 2023
4c4311b
chore: kotlinx.serialization 라이브러리 추가
tmdgh1592 Jun 1, 2023
d7e8da7
refactor: Dto에 Serializable 어노테이션 추가
tmdgh1592 Jun 1, 2023
4054061
feat: 각 화면별로 뒤로가기 기능 구현
tmdgh1592 Jun 1, 2023
558c694
refactor: 라인 포맷팅
tmdgh1592 Jun 1, 2023
4b6cfed
refactor: isolatedViewTypeConcatAdapter을 notIsolatedViewTypeConcatAda…
tmdgh1592 Jun 1, 2023
8a05f31
feat: 스낵바를 보여주는 확장함수 구현
tmdgh1592 Jun 1, 2023
15ef714
refactor: 상품 주문 완료시 스낵바로 메시지를 보여주도록 변경
tmdgh1592 Jun 1, 2023
87709b2
refactor: 사용하지 않는 클래스, 메서드 제거
tmdgh1592 Jun 1, 2023
9685128
refactor: ShoppingDatabase를 싱클톤으로 변경
tmdgh1592 Jun 1, 2023
c68c1f1
refactor: inject 메서드명을 명시적으로 변경
tmdgh1592 Jun 1, 2023
471bf9a
refactor: ui의 CartProduct typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
05e244c
refactor: ui의 OrderModel typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
3f9f6e8
refactor: ui의 OrderProductModel typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
f5a1fd1
refactor: ui의 UiPage typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
53e96a7
refactor: ui의 UiPayment typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
948c74f
refactor: ui의 PointUi typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
740390a
refactor: ui의 PriceUi typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
ea3d85a
refactor: ui의 UiProduct typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
aecf209
refactor: ui의 UiProductCount typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
aea0e58
refactor: ui의 UiRecentProduct typealias를 제거하고 Model suffix를 붙이도록 변경
tmdgh1592 Jun 1, 2023
2dd17a1
refactor: 함수 네이밍 통일화
tmdgh1592 Jun 1, 2023
f479f24
refactor: 재화 관련 도메인을 추상화
tmdgh1592 Jun 1, 2023
93b2b76
refactor: OrderPresenter 중복 코드를 메서드로 분리
tmdgh1592 Jun 2, 2023
9a6a1cd
refactor: Ui 모델 파라미터명을 cartProductModel로 변경
tmdgh1592 Jun 2, 2023
3e167d6
refactor: 네이밍 통일화
tmdgh1592 Jun 2, 2023
46b6475
refactor: abstract class를 interface로 변경
tmdgh1592 Jun 2, 2023
0196ee7
refactor: page 관련 뷰 업데이트를 하나의 추상 메서드로 변경
tmdgh1592 Jun 2, 2023
8f88737
refactor: OrderActivity의 showOrders 메서드명 변경
tmdgh1592 Jun 2, 2023
c33533c
refactor: PaymentRequest의 SerializedName 파라미터명 변경
tmdgh1592 Jun 2, 2023
1255597
test: ProductDetailPresenter 테스트 코드 작성
tmdgh1592 Jun 2, 2023
ab5d8e8
test: ShoppingPresenter 테스트 코드 작성
tmdgh1592 Jun 2, 2023
611aca8
test: OrderPresenter 테스트 코드 작성
tmdgh1592 Jun 2, 2023
6211ba8
feat: 응답에 대해 response의 body null 여부를 검사하도록 변경
tmdgh1592 Jun 2, 2023
59459aa
refactor: typealias가 적용되지 않은 ProductId에 typealias 적용
tmdgh1592 Jun 2, 2023
31df852
fix: item_order_history.xml에서 binding type이 맞지 않아 크래시 발생하는 버그 수정
tmdgh1592 Jun 2, 2023
743fb78
fix: item_order_history.xml에서 제품 이미지뷰에 scaleType 적용
tmdgh1592 Jun 2, 2023
c716daa
feat: 상세 주문 내역 뒤로가기 기능 구현
tmdgh1592 Jun 2, 2023
8a67f4f
feat: Gson의 SerializedName을 kotlinx-serialization의 SerialName으로 변경
tmdgh1592 Jun 2, 2023
6637bb7
chore: Gson 라이브러리 의존성 제거
tmdgh1592 Jun 2, 2023
ce80e22
fix: gson 라이브러리 import 제거
tmdgh1592 Jun 2, 2023
ccf431b
fix: 주문 상세 리사이클러뷰에서 아이템이 보여지지 않는 버그 수정
tmdgh1592 Jun 2, 2023
a9a1cbf
feat: 주문 상세 아이템의 상품 이미지에 scaleType(centerCrop) 적용
tmdgh1592 Jun 2, 2023
02836bc
fix: 결제 아이템 뷰의 높이를 wrap_content로 변경
tmdgh1592 Jun 2, 2023
e1fded7
fix: CounterView의 숫자를 클릭하면 뒤의 배경이 클릭되는 버그 수정
tmdgh1592 Jun 2, 2023
23e0295
refactor: Log 출력 제거
tmdgh1592 Jun 2, 2023
6deb885
refactor: 코드 포맷팅
tmdgh1592 Jun 2, 2023
7b5f9a4
fix: 토큰 설정이 안 되었던 버그 수정
tmdgh1592 Jun 4, 2023
a59cc1b
refactor: 주문 목록 가져오는 api body 형식 변경에 따른 dto 변경
tmdgh1592 Jun 4, 2023
5f2fb98
feat: 필요 없는 않는 json 변환 옵션 제거
tmdgh1592 Jun 4, 2023
f2f6a9b
refactor: toText() 시그니처 대신 toString()을 오버라이드하여 사용하도록 변경
tmdgh1592 Jun 4, 2023
4497d33
refactor: 제품 가져오기 페이지네이션 api 적용
tmdgh1592 Jun 4, 2023
a70f99e
refactor: 주문 목록 api 변경에 따른 스펙 변경
tmdgh1592 Jun 4, 2023
0bc40f5
refactor: findCartProductByProductId의 실패 콜백 람다의 인자 변경
tmdgh1592 Jun 5, 2023
f2bd29c
refactor: 장바구니에 담긴 제품 개수를 매번 서버에 요청하도록 변경
tmdgh1592 Jun 5, 2023
a82f66f
refactor: Repository 네이밍의 Impl을 Default로 변경
tmdgh1592 Jun 5, 2023
5c9cea7
feat: 주문 목록 페이징 구현
tmdgh1592 Jun 5, 2023
1e09e7a
fix: 쇼핑 리스트 페이지네이션이 동작하지 않는 버그 수정
tmdgh1592 Jun 5, 2023
1a46c16
refactor: 주문 목록 더 보기 기능에서 take 함수를 호출하지 않도록 변경
tmdgh1592 Jun 5, 2023
1a2b2ff
fix: 최근 상품 목록에서 장바구니 개수가 담기지 않는 버그 수정
tmdgh1592 Jun 5, 2023
39f71be
fix: 최근 상품 디테일 화면에서 상품 개수를 더해지지 않는 버그 수정
tmdgh1592 Jun 5, 2023
d9c98c2
fix: 커스텀뷰에서 TypedArray 인스턴스에 use 사용를 사용하는 대신 recycle() 메서드를 호출하도록 변경
tmdgh1592 Jun 5, 2023
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package woowacourse.shopping.data.repository

import android.util.Log
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
Expand Down Expand Up @@ -39,28 +40,41 @@ class DefaultCartRepository(private val service: CartService) : CartRepository {
onSuccess: (CartProduct) -> Unit,
onFailed: () -> Unit,
) {
getAllCartProducts(
onSuccess = { cartProducts ->
val cartProduct = cartProducts.find { it.product.id == productId } ?: run {
onFailed()
return@getAllCartProducts
service.findCartProductByProductId(productId).enqueue(object : Callback<CartGetResponse> {
override fun onResponse(
call: Call<CartGetResponse>,
response: Response<CartGetResponse>,
) {
Log.d("buna", response.code().toString())
if (response.isSuccessful && response.body() != null) {
onSuccess(response.body()!!.toDomain())
Copy link

Choose a reason for hiding this comment

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

위의 if문에서 response.body가 널이 아님을 체크했는데도 non-null assertion(!!)을 왜 사용해야하는지 궁금하지 않으신가요? 😄

Copy link
Member Author

@tmdgh1592 tmdgh1592 Jun 6, 2023

Choose a reason for hiding this comment

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

response.body() 메서드가 반환하는 타입을 살펴보면 원인을 알 수 있습니다.

public @Nullable T body() {
    return body;
}

response.body() 메서드는 Nullable한 타입을 반환하고 있습니다.
body 필드가 final이기 때문에, response.body()에 대해 null 체크를 한 번만 해주면 개발자는 이 값이 null이 아님을 알 수 있습니다.
하지만 컴파일러는 response.body() 내부에서 어떤 값을 반환하는데, 이 값이 Nullable하다 라는 정보만으로 검사하기 때문에 !!을 사용해주어야 합니다.

위 방법을 피하고 싶다면, 아래와 같이 코드를 작성해볼 수도 있을 것 같습니다.

val body = response.body()
if (response.isSuccessful && body!= null) {
    onSuccess(body.toDomain())

Copy link

Choose a reason for hiding this comment

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

잘 이해하고 계시네요. 👍
이런 경우에는 scope function (apply, also, let, with, run 등)을 활용하기도 합니다.

return
}
onSuccess(cartProduct)
},
onFailed = { onFailed() }
)
onFailed()
}

override fun onFailure(call: Call<CartGetResponse>, throwable: Throwable) {
onFailed()
}
})
}

override fun saveCartProductByProductId(
productId: ProductId,
onSuccess: () -> Unit,
onSuccess: (cartItemId: Int) -> Unit,
onFailed: (Throwable) -> Unit,
) {
service.saveCartProduct(requestBody = CartAddRequest(productId))
.enqueue(object : Callback<Unit> {
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
Log.d("buna", response.code().toString())
if (response.isSuccessful && response.body() != null) {
onSuccess()
val header = response.headers()["location"]
val cartItemId = header?.substringAfterLast("/")?.toIntOrNull() ?: run {
onFailed(Throwable(response.message()))
return
}
onSuccess(cartItemId)
return
}
onFailed(Throwable(response.message()))
Expand Down Expand Up @@ -129,13 +143,12 @@ class DefaultCartRepository(private val service: CartService) : CartRepository {
updateProductCountById(cartProduct.id, updatedCount, onSuccess, onFailed)
},
onFailed = {
saveCartProductByProductId(productId, onSuccess, onFailed)
findCartProductByProductId(
saveCartProductByProductId(
productId = productId,
onSuccess = { newCartProduct ->
updateProductCountById(newCartProduct.id, addCount, onSuccess, onFailed)
onSuccess = { cartItemId ->
updateProductCountById(cartItemId, addCount, onSuccess, onFailed)
},
onFailed = {}
onFailed = onFailed
)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ShoppingActivity : AppCompatActivity(), View,

private fun initShoppingToolbar() {
val cart = binding.shoppingToolBar.findItemActionView(R.id.cart)
cart?.setOnClickListener { presenter.navigateToCart() }
cart?.setOnClickListener { presenter.inquiryCart() }

binding.shoppingToolBar.setOnMenuItemClickListener { item ->
if (item?.itemId == R.id.order_list) presenter.inquiryOrders()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface ShoppingContract {
fun addCartProduct(product: ProductModel, addCount: Int = 1)
fun updateCartCount(cartProduct: CartProductModel, changedCount: Int)
fun increaseCartCount(product: ProductModel, addCount: Int)
fun navigateToCart()
fun inquiryCart()
fun inquiryProductDetail(cartProduct: CartProductModel)
fun inquiryRecentProductDetail(recentProduct: RecentProductModel)
fun inquiryOrders()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import woowacourse.shopping.model.mapper.toDomain
import woowacourse.shopping.model.mapper.toUi
import woowacourse.shopping.ui.shopping.ShoppingContract.Presenter
import woowacourse.shopping.ui.shopping.ShoppingContract.View
import woowacourse.shopping.util.collection.DistinctList

class ShoppingPresenter(
private val view: View,
Expand All @@ -30,10 +31,13 @@ class ShoppingPresenter(
sizePerPage: Int = 20,
) : Presenter {
private var cart = Cart()
private val products: DistinctList<Product> = DistinctList()
private var currentPage: Page = LoadMore(sizePerPage = sizePerPage)
private val cartProductCount: ProductCountModel
get() = ProductCountModel(cart.productCountInCart)

override fun fetchAll() {
fetchAllCartProducts()
loadProducts(currentPage)
fetchRecentProducts()
}

Expand All @@ -43,36 +47,10 @@ class ShoppingPresenter(

override fun loadMoreProducts() {
currentPage = currentPage.next()
loadProducts(currentPage)
updateCartView()
}

override fun addCartProduct(product: ProductModel, addCount: Int) {
cartRepository.saveCartProductByProductId(
productId = product.toDomain().id,
onSuccess = ::fetchAllCartProducts,
onFailed = { view.showCartProductSaveFailed() })
}

override fun updateCartCount(cartProduct: CartProductModel, changedCount: Int) {
cartRepository.updateProductCountById(
cartProductId = cartProduct.toDomain().id,
count = ProductCount(changedCount),
onSuccess = ::fetchAllCartProducts,
onFailed = { view.showCartCountChangedFailed() })
}

override fun increaseCartCount(product: ProductModel, addCount: Int) {
cartRepository.increaseProductCountByProductId(
productId = product.id,
addCount = ProductCount(addCount),
onSuccess = ::fetchAllCartProducts,
onFailed = { view.showCartCountChangedFailed() })
}

override fun navigateToCart() {
view.navigateToCart()
}

override fun inquiryProductDetail(cartProduct: CartProductModel) {
val recentProduct = RecentProduct(product = cartProduct.product.toDomain())
view.navigateToProductDetail(cartProduct.product)
Expand All @@ -83,57 +61,82 @@ class ShoppingPresenter(
view.navigateToProductDetail(recentProduct.product)
}

override fun inquiryCart() {
view.navigateToCart()
}

override fun inquiryOrders() {
view.navigateToOrderList()
}

private fun fetchAllCartProducts() {
productRepository.getProductsByPage(
page = currentPage.getPageForCheckHasNext(),
onSuccess = { products -> transformCountedCartProduct(products, ::updateCart) },
onFailed = { view.showProductLoadFailed() }
override fun addCartProduct(product: ProductModel, addCount: Int) {
cartRepository.saveCartProductByProductId(
product.toDomain().id,
onSuccess = { loadProducts(currentPage) },
onFailed = { view.showCartProductSaveFailed() },
)
}

private fun transformCountedCartProduct(
products: List<Product>,
onSuccess: (List<CartProduct>) -> Unit,
) {
cartRepository.getAllCartProducts(
onSuccess = { fetchedCartProducts ->
val countedCartProduct = products.map { product ->
fetchedCartProducts.find { it.productId == product.id } ?: CartProduct(
product = product,
selectedCount = ProductCount(0)
)
override fun updateCartCount(cartProduct: CartProductModel, changedCount: Int) {
cartRepository.updateProductCountById(
cartProduct.toDomain().id,
ProductCount(changedCount),
onSuccess = { loadProducts(currentPage) },
onFailed = { view.showCartCountChangedFailed() },
)
}

override fun increaseCartCount(product: ProductModel, addCount: Int) {
cartRepository.increaseProductCountByProductId(
product.id,
ProductCount(addCount),
onSuccess = {},
onFailed = { view.showCartCountChangedFailed() },
)
loadProducts(currentPage)
}

private fun loadProducts(page: Page) {
productRepository.getProductsByPage(
page = page.getPageForCheckHasNext(),
onSuccess = { fetchedProducts ->
products.addAll(fetchedProducts)
loadCartProducts { fetchedCartProducts ->
updateCart(transformCartProducts(products, fetchedCartProducts))
}
onSuccess(countedCartProduct)
},
onFailed = { view.showProductLoadFailed() },
)
}

private fun transformCartProducts(
products: List<Product>,
cartProducts: List<CartProduct>,
): List<CartProduct> = products.map { product ->
cartProducts.find { it.productId == product.id } ?: CartProduct(
product = product,
selectedCount = ProductCount(0),
)
}

private fun loadCartProducts(onLoaded: (List<CartProduct>) -> Unit) {
cartRepository.getAllCartProducts(
onSuccess = { cartProducts -> onLoaded(cartProducts) },
onFailed = {},
)
}

private fun updateCart(newCartProducts: List<CartProduct>) {
cart = cart.update(newCartProducts)
updateCartView()
}

private fun updateCartView() {
updateCartProductCount()
view.updateProducts(currentPage.takeItems(cart).toUi())
view.updateCartBadge(cartProductCount)
view.updateLoadMoreVisible()
}

private fun updateCartProductCount() {
cartRepository.getAllCartProducts(
onSuccess = {
view.updateCartBadge(ProductCountModel(Cart(it).productCountInCart))
},
onFailed = {
view.updateCartBadge(ProductCountModel(0))
})
}

private fun View.updateLoadMoreVisible() {
if (currentPage.hasNext(cart)) showLoadMoreButton() else hideLoadMoreButton()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package woowacourse.shopping.util.collection

class DistinctList<E> : ArrayList<E>() {
Copy link

Choose a reason for hiding this comment

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

중복으로 추가되지 않도록 하는 리스트를 구현해주셨네요.
잘 구현해주셨지만, 가급적이면 좀 더 검증된 함수 혹은 자료형을 활용해보면 어떨까요?

List의 함수 중에서는 distinct 함수를 제공하고 있어요.
이 함수를 사용하시거나, Set을 사용하셔도 됩니다.
실제로 distinct 함수 내부를 보면, Set으로 변환했다가 다시 List로 변환하고 있어요.

public fun <T> Iterable<T>.distinct(): List<T> {
    return this.toMutableSet().toList()
}

Copy link
Member Author

Choose a reason for hiding this comment

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

매번 distinct를 사용한다는 것이 불편하게 느껴져서 DistinctList 자료구조를 임의로 만들어 보았습니다!
하지만 말씀하신 것처럼 이미 검증된 함수를 사용하는 것이 더 오류를 줄이는 데 도움이 될 것이라는 생각이 듭니다 😃
고민을 해보았는데, 일반적인 Set은 순서를 보장하지 않기 때문에 LinkedHastSet 자료구조를 사용하도록 하였습니다!


override fun addAll(elements: Collection<E>): Boolean {
val temp = mutableListOf<E>()
elements.forEach { element ->
if (!contains(element)) {
temp.add(element)
}
}
return super.addAll(temp)
}

override fun add(element: E): Boolean {
if (!contains(element)) {
return super.add(element)
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal class ShoppingPresenterTest {
/* ... */

// when
presenter.navigateToCart()
presenter.inquiryCart()

// then
verify(exactly = 1) { view.navigateToCart() }
Expand All @@ -122,7 +122,7 @@ internal class ShoppingPresenterTest {
/* ... */

// when
presenter.navigateToCart()
presenter.inquiryCart()

// then
verify(exactly = 1) { view.navigateToCart() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface CartRepository {

fun saveCartProductByProductId(
productId: ProductId,
onSuccess: () -> Unit, onFailed: (Throwable) -> Unit,
onSuccess: (cartItemId: Int) -> Unit, onFailed: (Throwable) -> Unit,
)

fun updateProductCountById(
Expand Down