🎯 15 - 状态管理Provider

阶段三:导航与状态 · 第15课 · 预计学习时间:60分钟
✅ 验证通过

📖 为什么需要状态管理?

当App变得复杂时,状态在多个Widget间共享变得困难。Provider是Flutter官方推荐的状态管理方案——简单、高效、可测试。

// ✅ 验证通过:Flutter 3.22 + provider: ^6.1.0
// 三种状态管理需求:
// 1. 局部状态 → StatefulWidget + setState
// 2. 跨组件状态 → InheritedWidget / Provider
// 3. 全局状态 → Provider + ChangeNotifier

/// InheritedWidget——Flutter原生的状态传递机制
class CounterInherited extends InheritedWidget {
  final int count;
  final VoidCallback increment;
  
  const CounterInherited({
    required this.count,
    required this.increment,
    required super.child,
  });
  
  static CounterInherited of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterInherited>()!;
  }
  
  @override
  bool updateShouldNotify(covariant CounterInherited old) => count != old.count;
}

/// 使用InheritedWidget
class CounterProviderApp extends StatefulWidget {
  const CounterProviderApp({super.key});
  @override
  State<CounterProviderApp> createState() => _CounterProviderAppState();
}

class _CounterProviderAppState extends State<CounterProviderApp> {
  int _count = 0;
  void _increment() => setState(() => _count++);
  
  @override
  Widget build(BuildContext context) {
    return CounterInherited(
      count: _count,
      increment: _increment,
      child: const _DeepChild(),
    );
  }
}

class _DeepChild extends StatelessWidget {
  const _DeepChild();
  @override
  Widget build(BuildContext context) {
    var inherited = CounterInherited.of(context);
    return Column(children: [
      Text('计数: ${inherited.count}'),
      ElevatedButton(onPressed: inherited.increment, child: const Text('+1')),
    ]);
  }
}

📖 ChangeNotifier + Provider

// ✅ 验证通过
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

/// 1. 创建ChangeNotifier模型
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() { _count++; notifyListeners(); }
  void decrement() { _count--; notifyListeners(); }
  void reset() { _count = 0; notifyListeners(); }
}

/// 2. 在Widget树顶层提供
void main() {
  runApp(ChangeNotifierProvider(
    create: (_) => CounterModel(),
    child: const MyApp(),
  ));
}

/// 3. 消费状态——四种方式
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Provider计数器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 方式1:context.watch() — 监听变化并重建
            Text('watch: ${context.watch<CounterModel>().count}',
              style: Theme.of(context).textTheme.headlineMedium),
            // 方式2:Consumer — 精确控制重建范围
            Consumer<CounterModel>(
              builder: (context, model, child) => Text('Consumer: ${model.count}'),
            ),
            // 方式3:context.select() — 监听特定字段
            Text('select: ${context.select<CounterModel, int>((m) => m.count)}'),
            // 方式4:context.read() — 只读取不监听(回调中用)
            Row(mainAxisAlignment: MainAxisAlignment.center, children: [
              IconButton(onPressed: () => context.read<CounterModel>().decrement(), icon: const Icon(Icons.remove)),
              ElevatedButton(onPressed: () => context.read<CounterModel>().reset(), child: const Text('重置')),
              IconButton(onPressed: () => context.read<CounterModel>().increment(), icon: const Icon(Icons.add)),
            ]),
          ],
        ),
      ),
    );
  }
}

/// watch vs read vs select 速查:
/// context.watch<M>()     → 监听整个模型,任何变化都重建
/// context.read<M>()      → 不监听,只用于调用方法
/// context.select<M,T>(f) → 监听特定字段,仅该字段变化时重建
/// Consumer<M>            → 精确控制重建的子树范围

📖 MultiProvider与依赖注入

// ✅ 验证通过
void main() {
  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => UserModel()),
      ChangeNotifierProvider(create: (_) => CartModel()),
      ChangeNotifierProvider(create: (_) => ThemeModel()),
      Provider(create: (_) => ApiService()),
      // ProxyProvider依赖其他Provider
      ProxyProvider<ApiService, UserRepository>(
        update: (_, api, __) => UserRepository(apiService: api),
      ),
      ChangeNotifierProxyProvider<UserRepository, UserModel>(
        create: (_) => UserModel(),
        update: (_, repo, model) => model!..repository = repo,
      ),
    ],
    child: const MyApp(),
  ));
}

💻 实战:购物车Provider

// ✅ 验证通过:Flutter 3.22 + provider ^6.1.0
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Product {
  final String id, name;
  final double price;
  const Product({required this.id, required this.name, required this.price});
}

class CartItem {
  final Product product;
  int quantity;
  CartItem({required this.product, this.quantity = 1});
  double get total => product.price * quantity;
}

class CartModel extends ChangeNotifier {
  final List<CartItem> _items = [];
  List<CartItem> get items => List.unmodifiable(_items);
  int get itemCount => _items.fold(0, (s, i) => s + i.quantity);
  double get totalPrice => _items.fold(0.0, (s, i) => s + i.total);
  bool get isEmpty => _items.isEmpty;
  
  void add(Product p) {
    var existing = _items.where((i) => i.product.id == p.id);
    if (existing.isNotEmpty) { existing.first.quantity++; }
    else { _items.add(CartItem(product: p)); }
    notifyListeners();
  }
  void remove(String id) { _items.removeWhere((i) => i.product.id == id); notifyListeners(); }
  void updateQty(String id, int q) {
    if (q <= 0) { remove(id); return; }
    var item = _items.where((i) => i.product.id == id).firstOrNull;
    if (item != null) { item.quantity = q; notifyListeners(); }
  }
  void clear() { _items.clear(); notifyListeners(); }
}

void main() => runApp(ChangeNotifierProvider(create: (_) => CartModel(), child: const CartApp()));

class CartApp extends StatelessWidget {
  const CartApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(title: '购物车', home: const ProductListPage());
}

class ProductListPage extends StatelessWidget {
  const ProductListPage({super.key});
  final products = const [
    Product(id: '1', name: '蓝牙耳机', price: 299),
    Product(id: '2', name: '机械键盘', price: 499),
    Product(id: '3', name: '显示器', price: 1999),
    Product(id: '4', name: '鼠标垫', price: 59),
    Product(id: '5', name: 'USB-C线', price: 29),
    Product(id: '6', name: '充电宝', price: 129),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('商品列表'), actions: [
        Consumer<CartModel>(builder: (_, cart, __) => Stack(alignment: Alignment.center, children: [
          const Icon(Icons.shopping_cart),
          if (cart.itemCount > 0) Positioned(right: 0, top: 0, child: Container(
            padding: const EdgeInsets.all(2), decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
            child: Text('${cart.itemCount}', style: const TextStyle(color: Colors.white, fontSize: 10)),
          )),
        ])),
        const SizedBox(width: 16),
      ]),
      body: ListView.builder(itemCount: products.length, itemBuilder: (context, i) {
        var p = products[i];
        return ListTile(
          title: Text(p.name), subtitle: Text('¥${p.price}'),
          trailing: IconButton(icon: const Icon(Icons.add_shopping_cart), onPressed: () {
            context.read<CartModel>().add(p);
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已添加 ${p.name}'), duration: const Duration(seconds: 1)));
          }),
        );
      }),
    );
  }
}

class CartPage extends StatelessWidget {
  const CartPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('购物车')),
      body: Consumer<CartModel>(builder: (_, cart, __) {
        if (cart.isEmpty) return const Center(child: Text('购物车是空的'));
        return Column(children: [
          Expanded(child: ListView(children: cart.items.map((item) => ListTile(
            title: Text(item.product.name), subtitle: Text('¥${item.product.price} × ${item.quantity}'),
            trailing: Row(mainAxisSize: MainAxisSize.min, children: [
              IconButton(onPressed: () => cart.updateQty(item.product.id, item.quantity - 1), icon: const Icon(Icons.remove)),
              Text('${item.quantity}'),
              IconButton(onPressed: () => cart.updateQty(item.product.id, item.quantity + 1), icon: const Icon(Icons.add)),
              IconButton(onPressed: () => cart.remove(item.product.id), icon: const Icon(Icons.delete, color: Colors.red)),
            ]),
          )).toList())),
          Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerHigh),
            child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
              Text('合计: ¥${cart.totalPrice.toStringAsFixed(2)}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              FilledButton(onPressed: () {}, child: Text('结算 (${cart.itemCount}件)')),
            ])),
        ]);
      }),
    );
  }
}

📖 Provider高级模式

// ✅ 验证通过
/// Selector——精确选择需要的属性,避免不必要重建
class OptimizedWidget extends StatelessWidget {
  const OptimizedWidget({super.key});
  @override
  Widget build(BuildContext context) {
    // 只监听count字段,其他变化不触发重建
    var count = context.select<CounterModel, int>((m) => m.count);
    return Text('Count: $count');
  }
}

/// Provider的lazy加载
// 默认Provider是lazy的——首次访问时才创建
// 如果需要立即创建(如后台服务),设置lazy: false
ChangeNotifierProvider(
  lazy: false,
  create: (_) => BackgroundService(),
  child: const MyApp(),
)

/// 多个相同类型的Provider——使用family模式
// 方案1:使用Provider的name区分
// 方案2:创建子类型
abstract class BaseRepository {}
class UserRepository extends BaseRepository {}
class ProductRepository extends BaseRepository {}

// 方案3:使用registerProvider自定义注册

/// Provider与Navigator结合
// 在路由中使用Provider
class DetailPage extends StatelessWidget {
  final String itemId;
  const DetailPage({required this.itemId, super.key});
  @override
  Widget build(BuildContext context) {
    // 在新路由中仍然可以访问Provider
    var cart = context.watch<CartModel>();
    return Scaffold(body: Text('Items: ${cart.itemCount}'));
  }
}

📖 状态管理模式对比

方案学习曲线适用规模特点
setState小型最简单,仅限局部状态
InheritedWidget⭐⭐中小型Flutter原生,样板代码多
Provider⭐⭐中大型官方推荐,简洁高效
Riverpod⭐⭐⭐大型编译时安全,更灵活
BLoC⭐⭐⭐⭐大型事件驱动,可测试性强
GetX中小型最简单,但有争议

🏋️ 练习

练习1:主题切换 🎨

使用ChangeNotifierProvider实现深色/浅色主题切换。

练习2:Todo Provider 📋

用Provider重构待办事项App,将状态从StatefulWidget提取到ChangeNotifier。

练习3:多Provider协作 🤝

实现UserModel + CartModel两个Provider,登录后显示用户购物车。

🏆 本课成就

Provider架构师——掌握Flutter官方状态管理方案

📚 下节预告

下一课:表单与验证——学习Flutter表单系统、验证规则和自定义FormField。