Menu
CoddyTech

스토어: 장바구니에 추가

Coddy Flutter 여정의 프로젝트 섹션에 포함된 레슨. 36개 중 22번째.

장바구니는 두 화면에서 사용합니다. 상품 페이지에서는 상품을 장바구니에 추가하고, 그리드에서는 장바구니에 상품이 몇 개 있는지 표시합니다. 둘 이상의 화면에서 필요한 데이터는 모델에 넣고 Provider와 공유합니다.

class CartModel extends ChangeNotifier {
  final List<Product> _items = [];

  List<Product> get items => _items;
  int get count => _items.length;

  void add(Product product) {
    _items.add(product);
    notifyListeners();
  }
}

모든 화면에서 장바구니에 접근할 수 있도록 main에서 앱 전체를 ChangeNotifierProvider로 감쌉니다. 버튼은 장바구니만 변경하므로 context.read<CartModel>().add(product)를 사용합니다. 그리드에는 개수가 표시되므로 context.watch를 사용하며, notifyListeners가 실행될 때마다 그리드가 다시 빌드됩니다.

개수는 Badge를 사용해 장바구니 아이콘에 표시합니다. Badge는 앱에서 읽지 않은 메시지에 사용하는 작은 말풍선입니다. isLabelVisible: count > 0을 설정하면 장바구니가 비어 있을 때 표시되지 않습니다.

challenge icon

챌린지

어려움

공유 장바구니에 추가하기:

  • package:provider/provider.dart를 가져오고, 이론에서 배운 CartModel을 작성한 다음, 이를 생성하는 ChangeNotifierProvider로 MyApp을 감싸세요.
  • 상품 페이지에서 32픽셀의 여백 뒤에 Icons.add_shopping_cart와 Add to cart가 있는 FilledButton.icon을 추가하세요. 이 버튼은 상품을 추가하고, 이미 표시 중인 메시지를 숨긴 다음, Added Backpack to your cart라고 알리는 SnackBar를 표시합니다(상품 이름 포함).
  • 그리드의 AppBar actions에 개수가 표시된 Badge 안에 Icons.shopping_cart를 표시하고, 오른쪽에서 24픽셀 떨어지도록 하세요.

직접 해보기

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

void main() {
  runApp(const MyApp());
}

final money = NumberFormat.currency(locale: 'en_US', symbol: '\$');

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
      ),
      home: const ShopScreen(),
    );
  }
}

class Product {
  const Product({
    required this.name,
    required this.price,
    required this.icon,
    required this.color,
    required this.description,
  });

  final String name;
  final double price;
  final IconData icon;
  final MaterialColor color;
  final String description;
}

const List<Product> products = [
  Product(
    name: 'Headphones',
    price: 59,
    icon: Icons.headphones,
    color: Colors.indigo,
    description: 'Wireless headphones with 30 hours of battery.',
  ),
  Product(
    name: 'Watch',
    price: 129,
    icon: Icons.watch,
    color: Colors.teal,
    description: 'A smart watch that counts your steps.',
  ),
  Product(
    name: 'Backpack',
    price: 45.5,
    icon: Icons.backpack,
    color: Colors.orange,
    description: 'Room for a laptop, a lunch and a jacket.',
  ),
  Product(
    name: 'Mug',
    price: 12,
    icon: Icons.coffee,
    color: Colors.brown,
    description: 'Holds a big coffee and keeps it warm.',
  ),
  Product(
    name: 'Camera',
    price: 1299,
    icon: Icons.photo_camera,
    color: Colors.blueGrey,
    description: 'A mirrorless camera for sharp photos.',
  ),
  Product(
    name: 'Sneakers',
    price: 89.99,
    icon: Icons.directions_run,
    color: Colors.red,
    description: 'Light running shoes for every day.',
  ),
];

class ShopScreen extends StatelessWidget {
  const ShopScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Coddy Shop')),
      body: GridView.count(
        crossAxisCount: 2,
        padding: const EdgeInsets.all(16),
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
        childAspectRatio: 0.8,
        children: [
          for (Product product in products) ProductCard(product: product),
        ],
      ),
    );
  }
}

class ProductCard extends StatelessWidget {
  const ProductCard({super.key, required this.product});

  final Product product;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: InkWell(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => ProductScreen(product: product),
            ),
          );
        },
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(
              child: Container(
                width: double.infinity,
                alignment: Alignment.center,
                color: product.color.shade50,
                child: Icon(product.icon, size: 56, color: product.color),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    product.name,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    money.format(product.price),
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Theme.of(context).colorScheme.primary,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class ProductScreen extends StatelessWidget {
  const ProductScreen({super.key, required this.product});

  final Product product;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(title: Text(product.name)),
      body: ListView(
        padding: const EdgeInsets.all(24),
        children: [
          Container(
            height: 240,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: product.color.shade50,
              borderRadius: BorderRadius.circular(24),
            ),
            child: Icon(product.icon, size: 120, color: product.color),
          ),
          const SizedBox(height: 24),
          Text(
            money.format(product.price),
            style: textTheme.headlineSmall?.copyWith(
              fontWeight: FontWeight.bold,
              color: Theme.of(context).colorScheme.primary,
            ),
          ),
          const SizedBox(height: 12),
          Text(product.description, style: textTheme.bodyLarge),
        ],
      ),
    );
  }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

프로젝트의 모든 레슨

직접 연습해 보세요: 온라인 Flutter 컴파일러