[010] 플러터 (Flutter) 실전어플제작 - 쇼핑몰 앱 제작(로직구성2 - 장바구니 제품 수정)

2023. 12. 4. 20:18모바일어플개발/Flutter 실전어플 개발

반응형

안녕하세요~ totally 개발자입니다.

 

저번 시간에 이어 이번에는 장바구니 페이지에서 각 제품의 수량을 변경하거나 원하는 제품을 삭제하는 로직을 추가해보도록 하겠습니다.

아직 데이터베이스를 구성하기 전이므로 수동 리스트로 제품 상세 내용을 불러올 것입니다.

 

Step 1: item_basket_page.dart 파일을 열어주셔서 basketList를 아래 productList로 넣어줍니다.

class _ItemBasketPageState extends State<ItemBasketPage> {
  List<Product> productList = [
    Product(
        productNo: 1,
        productName: "노트북(Laptop)",
        productImageUrl: "https://picsum.photos/id/1/300/300",
        price: 600000),
    Product(
        productNo: 2,
        productName: "스마트폰(Phone)",
        productImageUrl: "https://picsum.photos/id/20/300/300",
        price: 500000),
    Product(
        productNo: 3,
        productName: "머그컵(Cup)",
        productImageUrl: "https://picsum.photos/id/30/300/300",
        price: 15000),
    Product(
        productNo: 4,
        productName: "키보드(Keyboard)",
        productImageUrl: "https://picsum.photos/id/60/300/300",
        price: 50000),
    Product(
        productNo: 5,
        productName: "포도(Grape)",
        productImageUrl: "https://picsum.photos/id/75/200/300",
        price: 75000),
    Product(
        productNo: 6,
        productName: "책(book)",
        productImageUrl: "https://picsum.photos/id/24/200/300",
        price: 24000),
  ];
  ....
}

 

 

Step 2: 50, 51번째 줄처럼 totalPrice 변수와 cartMap 변수를 선언 및 초기화합니다.

 

 

Step 3: initState에서 장바구니 리스트를 가져오고 총액을 계산해줍니다. 필자는 장바구니 리스트를 List 형태가 아닌 Map의 key: value 형태를 선택하였고 오직 제품번호와 수량만 저장하였기 때문에 별도의 데이터들을 가져오기 위해서 (여기에서는 가격), firstWhere을 활용해서 가격을 가져와서 계산하였습니다.

 

 

Step 4: body의 ListView.builder 부분을 아래처럼 만들어줍니다. 위와 같이 제품에서 필요한 상세 내용은 productList에서 가지고 옵니다.

 

 

Step 5: CachedNetworkImage 위젯의 이미지 높이를 130 정도로 설정해줍니다.

 

 

Step 6: 수량을 차감하는 부분입니다. 제품이 반드시 1개 이상이여야 하므로 1개 초과일 때만 차감시킬 수 있도록 조건문을 넣고 1개 초과 즉 2개 이상인 경우에 수량을 차감하고 디스크에 바로 반영해줍니다.

 

 

Step 7: 수량을 증가시키는 부분입니다. 위와 방식은 비슷하나 여기에서는 주문 개수 재고가 있는 경우에 if 문을 넣어 동일하게 처리할 수 있지만 필자는 생략하였습니다. 

 

 

Step 8: 장바구니에서 원하는 제품을 삭제하는 로직입니다. remove 함수를 사용하여 제거할 수 있습니다.

 

 

Step 9: 총액 계산 부분을 아래처럼 수정하여 옮겨줍니다.

 

 

Step 10: -, +, 삭제 버튼을 누르면 모두 잘 작동하며 다음 강좌에서는 아래 결제하기 

 

 

[전체 소스 코드]

 

import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:project/constants.dart';
import 'package:project/item_checkout_page.dart';
import 'package:project/models/product.dart';
class ItemBasketPage extends StatefulWidget {
const ItemBasketPage({super.key});
@override
State<ItemBasketPage> createState() => _ItemBasketPageState();
}
class _ItemBasketPageState extends State<ItemBasketPage> {
List<Product> productList = [
Product(
productNo: 1,
productName: "노트북(Laptop)",
productImageUrl: "https://picsum.photos/id/1/300/300",
price: 600000),
Product(
productNo: 2,
productName: "스마트폰(Phone)",
productImageUrl: "https://picsum.photos/id/20/300/300",
price: 500000),
Product(
productNo: 3,
productName: "머그컵(Cup)",
productImageUrl: "https://picsum.photos/id/30/300/300",
price: 15000),
Product(
productNo: 4,
productName: "키보드(Keyboard)",
productImageUrl: "https://picsum.photos/id/60/300/300",
price: 50000),
Product(
productNo: 5,
productName: "포도(Grape)",
productImageUrl: "https://picsum.photos/id/75/200/300",
price: 75000),
Product(
productNo: 6,
productName: "책(book)",
productImageUrl: "https://picsum.photos/id/24/200/300",
price: 24000),
];
double totalPrice = 0;
Map<String, dynamic> cartMap = {};
@override
void initState() {
super.initState();
//! 저장한 장바구니 리스트 가져오기
cartMap = json.decode(sharedPreferences.getString("cartMap") ?? "{}") ?? {};
}
double calculateTotalPrice() {
totalPrice = 0;
//! 총액 계산
for (int i = 0; i < cartMap.length; i++) {
totalPrice += productList
.firstWhere((element) =>
element.productNo == int.parse(cartMap.keys.elementAt(i)))
.price! *
cartMap[cartMap.keys.elementAt(i)];
}
return totalPrice;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("장바구니 페이지"),
centerTitle: true,
),
body: ListView.builder(
itemCount: cartMap.length,
itemBuilder: (context, index) {
int productNo = int.parse(cartMap.keys.elementAt(index));
Product currentProduct = productList
.firstWhere((element) => element.productNo == productNo);
return basketContainer(
productNo: productNo,
productName: currentProduct.productName ?? "",
productImageUrl: currentProduct.productImageUrl ?? "",
price: currentProduct.price ?? 0,
quantity: cartMap[productNo.toString()]);
},
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(20),
child: FilledButton(
onPressed: () {
//! 결제시작 페이지로 이동
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return const ItemCheckoutPage();
},
));
},
child: Text("총 ${numberFormat.format(calculateTotalPrice())}원 결제하기"),
),
),
);
}
Widget basketContainer({
required int productNo,
required String productName,
required String productImageUrl,
required double price,
required int quantity,
}) {
return Container(
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CachedNetworkImage(
imageUrl: productImageUrl,
width: MediaQuery.of(context).size.width * 0.3,
height: 130,
fit: BoxFit.cover,
placeholder: (context, url) {
return const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
);
},
errorWidget: (context, url, error) {
return const Center(
child: Text("오류 발생"),
);
},
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
productName,
textScaleFactor: 1.2,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Text("${numberFormat.format(price)}원"),
Row(
children: [
const Text("수량:"),
IconButton(
onPressed: () {
//! 수량 줄이기 (1 초과시에만 감소시킬 수 있음)
if (cartMap[productNo.toString()] > 1) {
setState(() {
//! 수량 1 차감
cartMap[productNo.toString()]--;
//! 디스크에 반영
sharedPreferences.setString(
"cartMap", json.encode(cartMap));
});
}
},
icon: const Icon(
Icons.remove,
),
),
Text("$quantity"),
IconButton(
onPressed: () {
setState(() {
//! 수량 늘리기
cartMap[productNo.toString()]++;
//! 디스크에 반영
sharedPreferences.setString(
"cartMap", json.encode(cartMap));
});
},
icon: const Icon(
Icons.add,
),
),
IconButton(
onPressed: () {
setState(() {
//! 장바구니에서 해당 제품 제거
cartMap.remove(productNo.toString());
//! 디스크에 반영
sharedPreferences.setString(
"cartMap", json.encode(cartMap));
});
},
icon: const Icon(
Icons.delete,
),
),
],
),
Text("합계: ${numberFormat.format(price * quantity)}원"),
],
),
),
],
),
);
}
}

 

[유튜브 강좌 영상]

 

https://youtu.be/0Ijg3qIcI_A

 

반응형