第12讲:入门级状态管理方案 - Provider详解

使用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包进行网络请求,让你的应用能够获取和提交真实的数据。

相关推荐
奋斗的小青年!!5 小时前
Flutter浮动按钮在OpenHarmony平台的实践经验
flutter·harmonyos·鸿蒙
程序员老刘8 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!11 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨13 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者9613 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨15 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei15 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei15 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!15 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_16 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter