Menu
CoddyTech

Giỏ hàng: Xóa mặt hàng

Thuộc phần Quản lý trạng thái trong hành trình Flutter của Coddy. Bài học 26 trên 27.

Người mua thêm nhầm món cần có cách xóa món đó: một nút xóa trên mỗi dòng trong giỏ hàng.

Giỏ hàng có thể chứa cùng một sản phẩm hai lần, vì vậy hãy xóa theo vị trí, không phải theo sản phẩm. Quy tắc trước đó vẫn áp dụng: không bao giờ thay đổi state ngay tại chỗ. Hãy tạo bản sao, thay đổi bản sao rồi đặt nó làm trạng thái mới:

void removeAt(int index) {
  final copy = [...state];
  copy.removeAt(index);
  state = copy;
}

Để biết vị trí của mỗi dòng, hãy lặp qua danh sách kèm chỉ số: for (var i = 0; i < cart.length; i++), rồi dùng cart[i] để lấy sản phẩm trên dòng đó.

Xóa một món và xem những thứ còn lại cập nhật theo: danh sách, tổng tiền và huy hiệu ở cửa hàng. Tất cả đều theo dõi cùng một giỏ hàng.

challenge icon

Thử thách

Khó

Trên màn hình giỏ hàng:

  • Thêm phương thức removeAt(int index) vào Cart để gán một danh sách mới không có mục đó.
  • Chuyển giá của mỗi hàng sang subtitle, và đặt trailing thành một IconButton có Icons.delete để xóa hàng đó.

Tự làm thử

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

class Product {
  const Product(this.name, this.price);

  final String name;
  final double price;
}

const products = [
  Product('Apple', 0.5),
  Product('Bread', 2.25),
  Product('Cheese', 4),
  Product('Milk', 1.2),
];

class Cart extends Notifier<List<Product>> {
  @override
  List<Product> build() => [];

  void add(Product product) {
    state = [...state, product];
  }
}

final cartProvider = NotifierProvider<Cart, List<Product>>(Cart.new);

final totalProvider = Provider((ref) {
  final cart = ref.watch(cartProvider);
  return cart.fold(0.0, (sum, product) => sum + product.price);
});

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ShopPage(),
    );
  }
}

class ShopPage extends ConsumerWidget {
  const ShopPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cart = ref.watch(cartProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shop'),
        actions: [
          IconButton(
            icon: Badge(
              label: Text('${cart.length}'),
              isLabelVisible: cart.isNotEmpty,
              child: const Icon(Icons.shopping_cart),
            ),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const CartScreen()),
              );
            },
          ),
        ],
      ),
      body: ListView(
        children: [
          for (final product in products)
            ListTile(
              title: Text(product.name),
              subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
              trailing: IconButton(
                icon: const Icon(Icons.add_shopping_cart),
                onPressed: () {
                  ref.read(cartProvider.notifier).add(product);
                },
              ),
            ),
        ],
      ),
    );
  }
}

class CartScreen extends ConsumerWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cart = ref.watch(cartProvider);
    final total = ref.watch(totalProvider);
    return Scaffold(
      appBar: AppBar(title: const Text('Your cart')),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(24),
        child: Text(
          'Total: \$${total.toStringAsFixed(2)}',
          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
      ),
      body: cart.isEmpty
          ? const Center(child: Text('Your cart is empty'))
          : ListView(
              children: [
                for (final product in cart)
                  ListTile(
                    title: Text(product.name),
                    trailing: Text('\$${product.price.toStringAsFixed(2)}'),
                  ),
              ],
            ),
    );
  }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Quản lý trạng thái

Tự luyện tập: Trình biên dịch Flutter online