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

Step2 pr 입니다! #74

Merged
merged 14 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion README.md
Copy link
Member

Choose a reason for hiding this comment

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

단지 확장 함수로 바꾸었는데
요렇게 달라지는 이유를 잘모르겠습니다;;
혹시 관련해서 조언을 얻을수 있을까요??

PR 본문에 남겨주신 질문에 대한 답변을 남깁니다!

가장 큰 원인은 수업 시간에도 잠깐 다뤘듯 Lambda가 현재는 불안정한 외부 값을 캡쳐하고 있어서 그렇습니다. 🫠
예를 들어 PR 본문에 남겨주신 아래와 같은 코드를,

fun NavGraphBuilder.productListScreen(
    onProductItemClicked:(clickedProductId: Int) -> Unit,
    onShoppingCartIconClicked: () -> Unit,
){
    composable(route = ScreenRouteType.SHOPPING_ITEM_LIST.navRoute) {
        ProductListRoute(
            onProductItemClicked = { clickedProductId ->
                onProductItemClicked(clickedProductId)
            },
            onShoppingCartIconClicked = {
                onShoppingCartIconClicked()
            }
        )
    }
}

다음과 같이 remember를 붙이면 recomposition이 발생하지 않을겁니다.

fun NavGraphBuilder.productListScreen(
    onProductItemClicked:(clickedProductId: Int) -> Unit,
    onShoppingCartIconClicked: () -> Unit,
){
    composable(route = ScreenRouteType.SHOPPING_ITEM_LIST.navRoute) {
        ProductListRoute(
            onProductItemClicked = remember { { clickedProductId ->
                onProductItemClicked(clickedProductId)
            } },
            onShoppingCartIconClicked = remember { {
                onShoppingCartIconClicked()
            } }
        )
    }
}

이렇게 불필요한 리컴포지션을 방지하기 위해 remember 붙이는 방법 외에도 여러 가지 방법이 있으니 이번 기회에 찾아보시는 것도 도움이 될거예요!

다음 글도 도움이 되길 바래요.

https://velog.io/@skydoves/compose-stability

Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# android-shopping-cart

## Step2

### 기능 요구 사항
- [x] 상품 상세 화면을 구현한다.
- [x] 상품 목록에서 상품을 누르면 상품 상세 화면으로 이동한다.
- [x] 뒤로 가기 버튼이나 아이콘을 누르면 직전 화면으로 돌아간다.
- [x] 장바구니 화면의 빈 껍데기를 연결한다.
- [x] 상품 목록에서 장바구니 아이콘을 누르면 장바구니 화면으로 이동한다.
- [x] 상품 상세에서 장바구니 담기 버튼을 누르면 장바구니 화면으로 이동한다.
- [x] 뒤로 가기 버튼이나 아이콘을 누르면 직전 화면으로 돌아간다.
- [x] 장바구니에 실제로 상품이 담기는 기능은 이 단계에서 고려하지 않는다

### 프로그래밍 요구 사항
- [x] 장바구니에 실제로 상품이 담기는 기능은 이 단계에서 고려하지 않는다.
- [x] 컴포저블 함수가 너무 많은 일을 하지 않도록 분리하기 위해 노력해 본다.
- [x] 의미있는 단위의 함수를 모아 별도의 파일로 분리해본다.

### 기능 구현 목록
- [x] 상품 목록 화면 툴바 구성하기
- [x] 성퓸 목록 mock data list 구성하기
- [x] 상품 목록 리스트 item 뷰 구성하기
- [x] 상품 목록 리스트 화면에 구현하기


## Step1

### 기능 요구 사항
Expand All @@ -15,4 +39,4 @@
- [x] 상품 목록 화면 툴바 구성하기
- [x] 성퓸 목록 mock data list 구성하기
- [x] 상품 목록 리스트 item 뷰 구성하기
- [x] 상품 목록 리스트 화면에 구현하기
- [x] 상품 목록 리스트 화면에 구현하기
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ android {
}

dependencies {
val nav_version = "2.7.7"
implementation("androidx.navigation:navigation-compose:$nav_version")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
Expand Down
9 changes: 1 addition & 8 deletions app/src/main/java/nextstep/shoppingcart/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import nextstep.shoppingcart.ui.theme.ShoppingCartTheme

class MainActivity : ComponentActivity() {
Expand All @@ -19,7 +12,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
ShoppingCartTheme {
ShoppingCartScreen()
MainScreen()
}
}
}
Expand Down
79 changes: 79 additions & 0 deletions app/src/main/java/nextstep/shoppingcart/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package nextstep.shoppingcart

import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import nextstep.shoppingcart.enums.ScreenRouteType
import nextstep.shoppingcart.navigation.navigateToProductDetail
import nextstep.shoppingcart.navigation.navigateToShoppingCart
import nextstep.shoppingcart.navigation.productDetailScreen
import nextstep.shoppingcart.navigation.productListScreen
import nextstep.shoppingcart.navigation.shoppingCartScreen
import nextstep.shoppingcart.ui.theme.ShoppingCartTheme


/**
* Create Date: 2024. 9. 2.
*
* 장바구니 앱 - 화면 이동 처리를 위한 메인 화면
* navhost 구성
* @author LeeDongHun
*
*
**/
@Composable
fun MainScreen(
modifier: Modifier = Modifier,
) {
val navHostController = rememberNavController()
NavHost(
modifier = modifier.padding(0.dp),
navController = navHostController,
startDestination = ScreenRouteType.SHOPPING_ITEM_LIST.navRoute,
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
// 상품 리스트 화면
productListScreen(
onShoppingCartIconClicked = {
navHostController.navigateToShoppingCart()
},
onProductItemClicked = { clickedProductId ->
navHostController.navigateToProductDetail(productId = clickedProductId)
}
)
// 상품 상세 화면
productDetailScreen(
addCartButtonClicked = {
navHostController.navigateToShoppingCart()
},
toolbarBackBtnClicked = {
navHostController.popBackStack()
}
)
// 쇼핑카트 화면
shoppingCartScreen(
toolbarBackBtnClicked = {
navHostController.popBackStack()
}
)
}
}

/**
* MainScreen 프리뷰 함수
*/
@Preview
@Composable
fun MainScreenPreview() {
ShoppingCartTheme {
MainScreen()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package nextstep.shoppingcart.component.button

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import nextstep.shoppingcart.R
import nextstep.shoppingcart.ui.theme.Blue50
import nextstep.shoppingcart.ui.theme.ShoppingCartTheme

/**
* Create Date: 2024. 9. 15.
*
* 장바구니 담기 버튼
* @author LeeDongHun
*
*
**/
@Composable
fun AddCartButton(
modifier: Modifier = Modifier,
addCartButtonClicked: () -> Unit = {}
) {
Comment on lines +29 to +33
Copy link
Member

Choose a reason for hiding this comment

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

컴포넌트를 나누는 기준은 개발자마다 제각각인데요,
별도의 파일로 분리하여 최대한 하나의 역할만 할 수 있도록 구현해주셨지만, 동시에 외부에 너무 많은 컴포넌트가 공개되어 어쩌면 캡슐화 가능한 컴포넌트도 드러나게 됩니다.(예를 들어 AddCartButton은 이름부터 장바구니에 추가하는 버튼을 의미하는데 나중에 재사용될 여지가 있을까 🤔 , 재사용 가능하다면 무조건 캡슐화해야 하나?)

이번 기회에 본인만의 기준을 새우는 과정이 되었으면 합니다!
다음 글이 도움되면 좋겠어요!

thdev.tech/compose/2024/08/04/Android-Compose-Split-Funcation

Button(
colors = ButtonDefaults.buttonColors(Blue50),
modifier = modifier
.height(54.dp)
.fillMaxWidth(),
shape = RectangleShape,
onClick = {
addCartButtonClicked()
}, content = {
Text(
text = stringResource(id = R.string.add_cart),
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
)
}
)
}

/**
* AddCartButton 프리뷰 함수
**/
@Preview
@Composable
fun AddCartButtonPreview() {
ShoppingCartTheme {
AddCartButton()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package nextstep.shoppingcart.component.image

import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import coil.compose.AsyncImage

/**
* Create Date: 2024. 9. 16.
*
* 상품 이미지는 리스트 화면이랑, 상세 화면에서 사용되므로 component화 시킴
* @author LeeDongHun
*
*
**/
@Composable
fun ProductImage(
productThumbnail:String,
imageRatio:ProductImageRatioType = ProductImageRatioType.SHOPPING_ITEM_THUMBNAIL_RATIO
){
AsyncImage(
modifier = Modifier.aspectRatio(imageRatio.ratio),
contentScale = ContentScale.Crop,
model = productThumbnail,
contentDescription = "상품 썸네일 이미지",
)
}

/**
* 상품 이미지 비율 타입
*/
enum class ProductImageRatioType(val ratio:Float) {
SHOPPING_ITEM_THUMBNAIL_RATIO(0.98f / 1f),
SHOPPING_ITEM_DETAIL_RATIO(1f)
}


/**
* ProductImage 프리뷰 함수
**/
@Preview(
showBackground = true,
backgroundColor = 0xFFFFFFFFL
)
@Composable
fun ProductImagePreview() {
ProductImage(
productThumbnail = "https://picsum.photos/200/300",
imageRatio = ProductImageRatioType.SHOPPING_ITEM_DETAIL_RATIO,
)
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,45 @@
package nextstep.shoppingcart.component.product

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import nextstep.shoppingcart.component.image.ProductImage
import nextstep.shoppingcart.component.image.ProductImageRatioType
import nextstep.shoppingcart.ui.theme.ShoppingCartTheme
import nextstep.shoppingcart.util.getLocalCurrencyFormat
import java.util.Locale

/**
* 쇼핑카트 상품목록 상품 아이템 이미지 비율
*/
const val SHOPPING_ITEM_THUMBNAIL_RATIO = 0.98f / 1f

/**
* 쇼핑카트 상품목록 상품 아이템
**/
@Composable
fun ShoppingItem(
fun ProductItem(
modifier: Modifier = Modifier,
productThumbnail:String,
productTitle: String,
productPrice: Long,
onItemClick: () -> Unit = {},
) {
Column(
modifier = modifier
modifier = modifier.clickable {
onItemClick()
},
) {
AsyncImage(
contentScale = ContentScale.Crop,
modifier = Modifier
.aspectRatio(SHOPPING_ITEM_THUMBNAIL_RATIO),
model = productThumbnail,
contentDescription = "상품 이미지",
ProductImage(
productThumbnail = productThumbnail,
imageRatio = ProductImageRatioType.SHOPPING_ITEM_THUMBNAIL_RATIO,
)
Text(
modifier = Modifier.padding(start = 5.dp),
Expand Down Expand Up @@ -73,9 +69,9 @@ fun ShoppingItem(
backgroundColor = 0xFFFFFFFFL
)
@Composable
fun ShoppingItemPreview() {
fun ProductItemPreview() {
ShoppingCartTheme {
ShoppingItem(
ProductItem(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
Expand All @@ -84,4 +80,4 @@ fun ShoppingItemPreview() {
productPrice = 100000,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ fun ProductList(
items = productList,
key = { item -> item.id }
) { item ->
ShoppingItem(
ProductItem(
modifier = Modifier.padding(5.dp),
productThumbnail = item.productThumbnail,
productTitle = item.productTitle,
productPrice = item.productPrice
productPrice = item.productPrice,
onItemClick = {
onItemClick(item)
}
)
}
}
Expand All @@ -65,4 +68,4 @@ fun ProductListPreview() {
productList = shoppingItemMockList,
onItemClick = {}
)
}
}
Loading