Menu
CoddyTech

Giỏ hàng: Thanh toán

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

Phần cuối cùng là thanh toán. Chỉ cần chạm một lần là hoàn thành ba việc: giỏ hàng trống, một thông báo xác nhận đơn hàng xuất hiện và người mua quay lại cửa hàng, nơi huy hiệu đã biến mất.

Việc làm trống giỏ hàng là một phương thức nữa của notifier, và nó đặt một danh sách trống mới:

void clear() {
  state = [];
}

Thanh toán khi giỏ hàng trống chẳng có ý nghĩa gì, vì vậy hãy làm mờ nút. Không thể nhấn nút có onPressed là null:

onPressed: cart.isEmpty ? null : () { ... },

Nhìn lại những gì bạn đã xây dựng: hai màn hình, một huy hiệu và tổng tiền, tất cả cùng đọc một giỏ hàng, không cần truyền giá trị từ màn hình này sang màn hình khác. Đó là quản lý trạng thái.

challenge icon

Thử thách

Trung bình

Bên dưới tổng tiền trên màn hình giỏ hàng, thêm nút Check out:

  • Bị làm mờ khi giỏ hàng trống.
  • Khi được nhấn, nút này làm trống giỏ hàng bằng phương thức (method) mới clear(), hiển thị SnackBar có nội dung Order placed! và quay lại cử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];
  }

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

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 (var i = 0; i < cart.length; i++)
                  ListTile(
                    title: Text(cart[i].name),
                    subtitle: Text('\$${cart[i].price.toStringAsFixed(2)}'),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () {
                        ref.read(cartProvider.notifier).removeAt(i);
                      },
                    ),
                  ),
              ],
            ),
    );
  }
}
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