使用Provider优雅地管理跨组件共享状态,告别繁琐的回调传递。

你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们深入学习了如何使用setState管理局部状态。但当状态需要在多个Widget之间共享时,setState就显得力不从心了。今天,我们将学习Flutter社区最受欢迎的状态管理方案之一——Provider

一、为什么需要状态管理?

1.1 setState 的局限性回顾

考虑这样一个场景:用户信息需要在多个页面共享

dart

复制

下载

// 使用setState实现,需要层层传递回调 - 非常繁琐!
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  User _user = User(name: '张三', email: 'zhangsan@example.com');

  void _updateUser(User newUser) {
    setState(() {
      _user = newUser;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(
        user: _user,
        onUserUpdated: _updateUser,
      ),
      routes: {
        '/profile': (context) => ProfilePage(
              user: _user,
              onUserUpdated: _updateUser,
            ),
        '/settings': (context) => SettingsPage(
              user: _user,
              onUserUpdated: _updateUser,
            ),
      },
    );
  }
}

1.2 Provider 的优势

  • 减少样板代码:无需手动传递状态和回调

  • 清晰的关注点分离:状态逻辑与UI逻辑分离

  • 性能优化:只有依赖状态的Widget会重建

  • 易于测试:可以轻松模拟状态进行测试

  • 官方推荐:Flutter团队推荐的状态管理方案


二、Provider 核心概念

2.1 Provider 的三要素

  1. ChangeNotifier:存储状态并通知监听者

  2. ChangeNotifierProvider:在Widget树中提供状态

  3. Consumer/Provider.of:在Widget中访问状态

2.2 工作原理

text

复制

下载

ChangeNotifier(状态) 
    ↓ 通知变化
ChangeNotifierProvider(提供者)
    ↓ 提供数据
Consumer(消费者)→ 重建UI

三、Provider 实战:购物车案例

让我们通过一个完整的购物车案例来学习Provider的使用。

步骤1:添加依赖

在 pubspec.yaml 中添加Provider依赖:

yaml

复制

下载

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1 # 添加Provider依赖

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

运行 flutter pub get 安装依赖。

步骤2:创建模型类

lib/models/product.dart

dart

复制

下载

class Product {
  final String id;
  final String name;
  final String description;
  final double price;
  final String imageUrl;

  const Product({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    required this.imageUrl,
  });

  // 拷贝方法,用于创建修改后的副本
  Product copyWith({
    String? id,
    String? name,
    String? description,
    double? price,
    String? imageUrl,
  }) {
    return Product(
      id: id ?? this.id,
      name: name ?? this.name,
      description: description ?? this.description,
      price: price ?? this.price,
      imageUrl: imageUrl ?? this.imageUrl,
    );
  }
}

步骤3:创建购物车项模型

lib/models/cart_item.dart

dart

复制

下载

class CartItem {
  final Product product;
  int quantity;

  CartItem({
    required this.product,
    this.quantity = 1,
  });

  double get totalPrice => product.price * quantity;

  // 拷贝方法
  CartItem copyWith({
    Product? product,
    int? quantity,
  }) {
    return CartItem(
      product: product ?? this.product,
      quantity: quantity ?? this.quantity,
    );
  }
}

步骤4:创建购物车状态管理类

lib/providers/cart_provider.dart

dart

复制

下载

import 'package:flutter/foundation.dart';
import '../models/cart_item.dart';
import '../models/product.dart';

class CartProvider with ChangeNotifier {
  // 存储购物车商品
  final List<CartItem> _items = [];

  // 获取购物车中的所有商品(只读)
  List<CartItem> get items => List.unmodifiable(_items);

  // 获取购物车商品总数
  int get totalItems {
    return _items.fold(0, (total, item) => total + item.quantity);
  }

  // 获取购物车总金额
  double get totalAmount {
    return _items.fold(0.0, (total, item) => total + item.totalPrice);
  }

  // 添加商品到购物车
  void addItem(Product product) {
    final index = _items.indexWhere((item) => item.product.id == product.id);
    
    if (index >= 0) {
      // 商品已存在,增加数量
      _items[index] = _items[index].copyWith(
        quantity: _items[index].quantity + 1,
      );
    } else {
      // 商品不存在,添加新项
      _items.add(CartItem(product: product));
    }
    
    // 通知监听者状态已改变
    notifyListeners();
  }

  // 从购物车移除商品
  void removeItem(String productId) {
    final index = _items.indexWhere((item) => item.product.id == productId);
    if (index >= 0) {
      _items.removeAt(index);
      notifyListeners();
    }
  }

  // 清空购物车
  void clear() {
    _items.clear();
    notifyListeners();
  }

  // 更新商品数量
  void updateQuantity(String productId, int newQuantity) {
    final index = _items.indexWhere((item) => item.product.id == productId);
    if (index >= 0) {
      if (newQuantity <= 0) {
        _items.removeAt(index);
      } else {
        _items[index] = _items[index].copyWith(quantity: newQuantity);
      }
      notifyListeners();
    }
  }

  // 检查商品是否在购物车中
  bool contains(String productId) {
    return _items.any((item) => item.product.id == productId);
  }

  // 获取指定商品的数量
  int getQuantity(String productId) {
    final item = _items.firstWhere(
      (item) => item.product.id == productId,
      orElse: () => CartItem(product: Product(id: '', name: '', description: '', price: 0, imageUrl: '')),
    );
    return item.quantity;
  }
}

步骤5:配置Provider

lib/main.dart

dart

复制

下载

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/cart_provider.dart';
import 'screens/product_list_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // 提供购物车状态
        ChangeNotifierProvider(create: (context) => CartProvider()),
      ],
      child: MaterialApp(
        title: 'Provider购物车示例',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          useMaterial3: true,
        ),
        home: const ProductListScreen(),
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

步骤6:创建商品列表页面

lib/screens/product_list_screen.dart

dart

复制

下载

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/product.dart';
import '../providers/cart_provider.dart';
import 'cart_screen.dart';

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

  // 模拟商品数据
  final List<Product> _products = const [
    Product(
      id: '1',
      name: '无线蓝牙耳机',
      description: '高品质音效,超长续航',
      price: 299.0,
      imageUrl: 'assets/headphones.jpg',
    ),
    Product(
      id: '2',
      name: '智能手机',
      description: '最新款旗舰手机',
      price: 3999.0,
      imageUrl: 'assets/phone.jpg',
    ),
    Product(
      id: '3',
      name: '智能手表',
      description: '健康监测,运动助手',
      price: 899.0,
      imageUrl: 'assets/watch.jpg',
    ),
    Product(
      id: '4',
      name: '笔记本电脑',
      description: '轻薄便携,性能强劲',
      price: 5999.0,
      imageUrl: 'assets/laptop.jpg',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('商品列表'),
        actions: [
          // 购物车图标,显示商品数量
          Consumer<CartProvider>(
            builder: (context, cart, child) {
              return Stack(
                children: [
                  IconButton(
                    icon: const Icon(Icons.shopping_cart),
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => const CartScreen(),
                        ),
                      );
                    },
                  ),
                  if (cart.totalItems > 0)
                    Positioned(
                      right: 8,
                      top: 8,
                      child: Container(
                        padding: const EdgeInsets.all(2),
                        decoration: const BoxDecoration(
                          color: Colors.red,
                          shape: BoxShape.circle,
                        ),
                        constraints: const BoxConstraints(
                          minWidth: 16,
                          minHeight: 16,
                        ),
                        child: Text(
                          '${cart.totalItems}',
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 10,
                            fontWeight: FontWeight.bold,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                ],
              );
            },
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: _products.length,
        itemBuilder: (context, index) {
          final product = _products[index];
          return ProductItem(product: product);
        },
      ),
    );
  }
}

class ProductItem extends StatelessWidget {
  final Product product;

  const ProductItem({super.key, required this.product});

  @override
  Widget build(BuildContext context) {
    // 使用Consumer来监听购物车状态
    return Consumer<CartProvider>(
      builder: (context, cart, child) {
        final isInCart = cart.contains(product.id);
        final quantityInCart = cart.getQuantity(product.id);

        return Card(
          margin: const EdgeInsets.all(8),
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              children: [
                // 商品图片
                Container(
                  width: 80,
                  height: 80,
                  decoration: BoxDecoration(
                    color: Colors.grey[200],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Icon(Icons.shopping_bag, color: Colors.grey),
                ),
                const SizedBox(width: 16),
                
                // 商品信息
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        product.name,
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        product.description,
                        style: TextStyle(
                          color: Colors.grey[600],
                          fontSize: 14,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        '¥${product.price.toStringAsFixed(2)}',
                        style: const TextStyle(
                          color: Colors.red,
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                    ],
                  ),
                ),
                
                // 购物车操作按钮
                Column(
                  children: [
                    if (isInCart) ...[
                      Text(
                        '已添加: $quantityInCart',
                        style: const TextStyle(
                          color: Colors.green,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Row(
                        children: [
                          IconButton(
                            icon: const Icon(Icons.remove),
                            onPressed: () {
                              cart.updateQuantity(product.id, quantityInCart - 1);
                            },
                            style: IconButton.styleFrom(
                              backgroundColor: Colors.grey[200],
                            ),
                          ),
                          const SizedBox(width: 8),
                          IconButton(
                            icon: const Icon(Icons.add),
                            onPressed: () {
                              cart.updateQuantity(product.id, quantityInCart + 1);
                            },
                            style: IconButton.styleFrom(
                              backgroundColor: Colors.grey[200],
                            ),
                          ),
                        ],
                      ),
                    ] else
                      ElevatedButton.icon(
                        onPressed: () {
                          cart.addItem(product);
                        },
                        icon: const Icon(Icons.add_shopping_cart),
                        label: const Text('加入购物车'),
                      ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

步骤7:创建购物车页面

lib/screens/cart_screen.dart

dart

复制

下载

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
      ),
      body: Consumer<CartProvider>(
        builder: (context, cart, child) {
          if (cart.items.isEmpty) {
            return const Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.shopping_cart_outlined, size: 64, color: Colors.grey),
                  SizedBox(height: 16),
                  Text(
                    '购物车是空的',
                    style: TextStyle(fontSize: 18, color: Colors.grey),
                  ),
                ],
              ),
            );
          }

          return Column(
            children: [
              // 商品列表
              Expanded(
                child: ListView.builder(
                  itemCount: cart.items.length,
                  itemBuilder: (context, index) {
                    final item = cart.items[index];
                    return CartItemWidget(item: item);
                  },
                ),
              ),
              
              // 底部汇总栏
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.white,
                  border: Border.top: BorderSide(color: Colors.grey.shade300),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.grey.withOpacity(0.2),
                      blurRadius: 8,
                      offset: const Offset(0, -2),
                    ),
                  ],
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '共 ${cart.totalItems} 件商品',
                          style: const TextStyle(fontSize: 14),
                        ),
                        Text(
                          '合计: ¥${cart.totalAmount.toStringAsFixed(2)}',
                          style: const TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                            color: Colors.red,
                          ),
                        ),
                      ],
                    ),
                    ElevatedButton(
                      onPressed: () {
                        _showCheckoutDialog(context, cart);
                      },
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 32,
                          vertical: 16,
                        ),
                      ),
                      child: const Text('立即结算'),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  void _showCheckoutDialog(BuildContext context, CartProvider cart) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认订单'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('商品数量: ${cart.totalItems}'),
            Text('订单金额: ¥${cart.totalAmount.toStringAsFixed(2)}'),
            const SizedBox(height: 16),
            const Text('确定要结算吗?'),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              cart.clear();
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('订单提交成功!')),
              );
            },
            child: const Text('确认'),
          ),
        ],
      ),
    );
  }
}

class CartItemWidget extends StatelessWidget {
  final CartItem item;

  const CartItemWidget({super.key, required this.item});

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
      child: ListTile(
        leading: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.circular(8),
          ),
          child: const Icon(Icons.shopping_bag, color: Colors.grey),
        ),
        title: Text(item.product.name),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('¥${item.product.price.toStringAsFixed(2)}'),
            const SizedBox(height: 4),
            Consumer<CartProvider>(
              builder: (context, cart, child) {
                return Row(
                  children: [
                    IconButton(
                      icon: const Icon(Icons.remove, size: 18),
                      onPressed: () {
                        cart.updateQuantity(item.product.id, item.quantity - 1);
                      },
                    ),
                    Text('${item.quantity}'),
                    IconButton(
                      icon: const Icon(Icons.add, size: 18),
                      onPressed: () {
                        cart.updateQuantity(item.product.id, item.quantity + 1);
                      },
                    ),
                  ],
                );
              },
            ),
          ],
        ),
        trailing: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              '¥${item.totalPrice.toStringAsFixed(2)}',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
              ),
            ),
            Consumer<CartProvider>(
              builder: (context, cart, child) {
                return IconButton(
                  icon: const Icon(Icons.delete, color: Colors.red),
                  onPressed: () {
                    cart.removeItem(item.product.id);
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

四、Provider 的多种使用方式

4.1 Consumer vs Provider.of

dart

复制

下载

// 方式1:使用Consumer(推荐)
Consumer<CartProvider>(
  builder: (context, cart, child) {
    return Text('商品数量: ${cart.totalItems}');
  },
)

// 方式2:使用Provider.of(在build方法中)
Widget build(BuildContext context) {
  final cart = Provider.of<CartProvider>(context);
  return Text('商品数量: ${cart.totalItems}');
}

// 方式3:使用Provider.of(在方法中,不监听变化)
void someMethod(BuildContext context) {
  final cart = Provider.of<CartProvider>(context, listen: false);
  cart.addItem(product);
}

4.2 Selector:精确重建

dart

复制

下载

// 只有totalItems改变时才重建,性能更好
Selector<CartProvider, int>(
  selector: (context, cart) => cart.totalItems,
  builder: (context, totalItems, child) {
    return Text('商品数量: $totalItems');
  },
)

五、最佳实践

5.1 状态分类

  • 使用 Provider 管理共享状态(用户信息、购物车、主题等)

  • 使用 setState 管理局部状态(动画、表单输入等)

5.2 性能优化

  • 使用 Selector 替代 Consumer 进行精确重建

  • 将不变的Widget提取到 child 参数中

  • 避免在build方法中创建新的对象

5.3 代码组织

  • 按功能模块组织Provider

  • 使用不可变数据模型

  • 为复杂业务逻辑创建独立的Service类

结语

恭喜!通过本讲的学习,你已经掌握了Provider这一强大的状态管理工具。你现在可以:

  • 理解为什么需要状态管理

  • 使用ChangeNotifier创建状态类

  • 使用Provider在Widget树中提供状态

  • 使用Consumer和Selector消费状态

  • 构建复杂的跨组件状态共享应用

Provider让状态管理变得简单而优雅,是中小型Flutter应用的理想选择。

在下一讲中,我们将学习如何与后端API通信,使用http包进行网络请求,让你的应用能够获取和提交真实的数据。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐