flutter学习第 11 节:状态管理进阶:Provider

在 Flutter 应用开发中,随着应用规模的扩大,组件之间的状态共享和通信会变得越来越复杂。当多个组件需要访问和修改同一状态时,传统的通过构造函数传递参数的方式会导致代码冗余、耦合度高且难以维护。本节课将介绍 Flutter 中常用的状态管理方案 ------Provider,它基于 InheritedWidget 实现,能够简洁高效地解决跨组件状态共享问题。

一、为什么需要状态管理

在 Flutter 中,所有 UI 都是由 Widget 构成的,而 Widget 是不可变的,状态(State)则是 Widget 中可以变化的数据。当应用简单时,我们可以通过 setState() 管理单个组件的状态,但当状态需要在多个组件之间共享时,就会面临以下问题:

  1. 跨组件通信困难:深层嵌套的子组件需要访问父组件的状态时,必须通过层层传递参数,形成 "prop drilling" 问题
  2. 状态同步复杂:多个组件依赖同一状态时,手动同步状态会导致代码逻辑混乱
  3. 代码可维护性差:状态分散在各个组件中,修改和调试变得困难
  4. 性能问题:不合理的状态管理会导致不必要的组件重建,影响应用性能

举个例子,一个电商应用中的购物车状态可能需要在商品列表、购物车页面、结算页面等多个地方访问和修改。如果使用传统方式,需要在各个页面之间传递购物车数据,当购物车发生变化时,还要手动通知所有相关页面更新,这显然是低效且容易出错的。

状态管理方案正是为了解决这些问题而诞生的,Provider 是其中最简单易用且官方推荐的方案之一。


二、Provider 基本概念与核心类

Provider 是基于 Flutter 原生 InheritedWidget 实现的状态管理库,它的核心思想是将共享状态抽离到一个独立的类中,然后通过一个 Provider 组件在 Widget 树中提供这个状态,最后在需要使用该状态的子组件中进行消费。

1. 核心类介绍

  • ChangeNotifier :一个实现了观察者模式的类,当状态发生变化时,调用 notifyListeners() 方法通知所有监听者更新
  • ChangeNotifierProvider :一个 Widget,用于在 Widget 树中提供 ChangeNotifier 实例,使其子树中的 Widget 可以访问该实例
  • Consumer :用于在子 Widget 中获取并监听 ChangeNotifier 实例,当状态变化时会重建自身
  • Provider.of :另一种获取 ChangeNotifier 实例的方法,可以选择是否监听状态变化

2. 安装 Provider

在使用 Provider 之前,需要在 pubspec.yaml 中添加依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.5  # 请使用最新版本

然后运行 flutter pub get 安装依赖。


三、Provider 基本使用流程

使用 Provider 管理状态通常遵循以下步骤:

  1. 创建一个继承自 ChangeNotifier 的状态类,封装需要共享的状态和修改状态的方法
  2. 使用 ChangeNotifierProvider 在 Widget 树的适当位置提供该状态实例
  3. 在需要使用状态的子 Widget 中,通过 ConsumerProvider.of 获取状态并使用

下面通过一个简单的计数器示例来演示基本用法:

1. 创建状态类

dart 复制代码
import 'package:flutter/foundation.dart';

// 继承 ChangeNotifier
class Counter with ChangeNotifier {
  int _count = 0;

  // 提供获取状态的方法
  int get count => _count;

  // 提供修改状态的方法
  void increment() {
    _count++;
    // 通知所有监听者
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}

2. 提供状态

在 Widget 树中使用 ChangeNotifierProvider 提供状态,通常放在应用的根部:

dart 复制代码
import 'package:provider/provider.dart';

void main() {
  runApp(
    // 提供 Counter 实例
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Demo',
      home: const CounterPage(),
    );
  }
}

3. 消费状态

在子 Widget 中通过 ConsumerProvider.of 获取并使用状态:

使用 Consumer

dart 复制代码
import 'package:provider/provider.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter Demo')),
      body: Center(
        // 使用 Consumer 获取 Counter 实例
        child: Consumer<Counter>(
          builder: (context, counter, child) {
            // builder 方法会在状态变化时重建
            return Text(
              'Current count: ${counter.count}',
              style: const TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              // 使用 Provider.of 获取实例(不监听变化)
              Provider.of<Counter>(context, listen: false).decrement();
            },
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 10),
          FloatingActionButton(
            onPressed: () {
              Provider.of<Counter>(context, listen: false).increment();
            },
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Consumer 性能优化

Consumerchild 参数可以用于优化性能,避免不必要的重建:

dart 复制代码
Consumer<Counter>(
  builder: (context, counter, child) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 这个 Text 会在状态变化时重建
        Text('Count: ${counter.count}'),
        // 这个子组件不会重建,因为它被提取到了 child 参数中
        child!,
      ],
    );
  },
  // 这个子组件只会构建一次
  child: const Text('This is a static text'),
)

使用 Provider.of

Provider.of<T>(context) 会获取最近的 T 类型的 Provider,并在状态变化时重建当前 Widget:

dart 复制代码
// 监听状态变化,状态变化时会重建当前 Widget
final counter = Provider.of<Counter>(context);

// 不监听状态变化,通常用于修改状态的场景
final counter = Provider.of<Counter>(context, listen: false);

使用示例:

dart 复制代码
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    // 获取 Counter 实例并监听变化
    final counter = Provider.of<Counter>(context);

    return Text(
      'Count: ${counter.count}',
      style: const TextStyle(fontSize: 24),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        // 获取 Counter 实例但不监听变化
        Provider.of<Counter>(context, listen: false).increment();
      },
      child: const Icon(Icons.add),
    );
  }
}

四、多个状态管理

当应用中有多个独立的状态需要管理时,可以使用 MultiProvider 来组织多个 Provider:

dart 复制代码
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => Counter()),
        ChangeNotifierProvider(create: (context) => ThemeProvider()),
        ChangeNotifierProvider(create: (context) => UserProvider()),
      ],
      child: const MyApp(),
    ),
  );
}

这样,在子组件中可以分别获取不同的状态:

dart 复制代码
// 获取计数器状态
final counter = Provider.of<Counter>(context);

// 获取主题状态
final themeProvider = Provider.of<ThemeProvider>(context);

五、实例:用 Provider 管理购物车状态

下面通过一个完整的购物车示例,展示如何使用 Provider 管理复杂状态:

1. 定义数据模型

dart 复制代码
// 商品模型
class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

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

// 购物车项模型
class CartItem {
  final String id;
  final String productId;
  final String name;
  final int quantity;
  final double price;

  CartItem({
    required this.id,
    required this.productId,
    required this.name,
    required this.quantity,
    required this.price,
  });

  // 计算小计
  double get totalPrice => price * quantity;
}

2. 创建购物车状态管理类

dart 复制代码
import 'package:flutter/foundation.dart';
import 'dart:collection';

class Cart with ChangeNotifier {
  // 存储购物车项,键为商品ID
  final Map<String, CartItem> _items = {};

  // 提供不可修改的购物车项视图
  UnmodifiableMapView<String, CartItem> get items => UnmodifiableMapView(_items);

  // 获取购物车项数量
  int get itemCount => _items.length;

  // 计算购物车总价
  double get totalAmount {
    var total = 0.0;
    _items.forEach((key, cartItem) {
      total += cartItem.price * cartItem.quantity;
    });
    return total;
  }

  // 添加商品到购物车
  void addItem(Product product) {
    if (_items.containsKey(product.id)) {
      // 如果商品已在购物车中,增加数量
      _items.update(
        product.id,
            (existingItem) => CartItem(
          id: existingItem.id,
          productId: existingItem.productId,
          name: existingItem.name,
          quantity: existingItem.quantity + 1,
          price: existingItem.price,
        ),
      );
    } else {
      // 如果商品不在购物车中,添加新项
      _items.putIfAbsent(
        product.id,
            () => CartItem(
          id: DateTime.now().toString(),
          productId: product.id,
          name: product.name,
          quantity: 1,
          price: product.price,
        ),
      );
    }
    notifyListeners();
  }

  // 从购物车中移除商品
  void removeItem(String productId) {
    _items.remove(productId);
    notifyListeners();
  }

  // 减少购物车中商品的数量
  void removeSingleItem(String productId) {
    if (!_items.containsKey(productId)) {
      return;
    }

    if (_items[productId]!.quantity > 1) {
      _items.update(
        productId,
            (existingItem) => CartItem(
          id: existingItem.id,
          productId: existingItem.productId,
          name: existingItem.name,
          quantity: existingItem.quantity - 1,
          price: existingItem.price,
        ),
      );
    } else {
      _items.remove(productId);
    }
    notifyListeners();
  }

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

3. 创建商品列表状态类

dart 复制代码
class Products with ChangeNotifier {
  final List<Product> _items = [
    Product(
      id: 'p1',
      name: 'Red Shirt',
      price: 29.99,
      imageUrl: 'https://picsum.photos/200/300?random=1',
    ),
    Product(
      id: 'p2',
      name: 'Trousers',
      price: 59.99,
      imageUrl: 'https://picsum.photos/200/300?random=2',
    ),
    Product(
      id: 'p3',
      name: 'Yellow Scarf',
      price: 19.99,
      imageUrl: 'https://picsum.photos/200/300?random=3',
    ),
    Product(
      id: 'p4',
      name: 'A Shoes',
      price: 99.99,
      imageUrl: 'https://picsum.photos/200/300?random=4',
    ),
  ];

  // 获取商品列表
  List<Product> get items => [..._items];
}

4. 在应用中提供状态

dart 复制代码
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (ctx) => Products()),
        ChangeNotifierProvider(create: (ctx) => Cart()),
      ],
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shopping Cart Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ProductsOverviewScreen(),
      routes: {
        CartScreen.routeName: (ctx) => const CartScreen(),
      },
    );
  }
}

5. 实现商品列表页面

dart 复制代码
class ProductsOverviewScreen extends StatelessWidget {
  const ProductsOverviewScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // 获取商品列表
    final productsData = Provider.of<Products>(context);
    final products = productsData.items;

    return Scaffold(
      appBar: AppBar(
        title: const Text('My Shop'),
        actions: [
          // 购物车图标,显示商品数量
          Consumer<Cart>(
            builder: (ctx, cart, ch) =>
                Badge(label: Text(cart.itemCount.toString()), child: ch!),
            child: IconButton(
              icon: const Icon(Icons.shopping_cart),
              onPressed: () {
                Navigator.of(context).pushNamed(CartScreen.routeName);
              },
            ),
          ),
        ],
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: products.length,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        itemBuilder: (ctx, i) => ProductItem(products[i]),
      ),
    );
  }
}

class ProductItem extends StatelessWidget {
  final Product product;

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

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          title: Text(product.name, textAlign: TextAlign.center),
          trailing: IconButton(
            icon: const Icon(Icons.shopping_cart),
            onPressed: () {
              // 将商品添加到购物车
              Provider.of<Cart>(context, listen: false).addItem(product);
              // 显示添加成功提示
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: const Text('Added item to cart!'),
                  duration: const Duration(seconds: 2),
                  action: SnackBarAction(
                    label: 'UNDO',
                    onPressed: () {
                      Provider.of<Cart>(
                        context,
                        listen: false,
                      ).removeSingleItem(product.id);
                    },
                  ),
                ),
              );
            },
            color: Theme.of(context).colorScheme.secondary,
          ),
        ),
        child: Image.network(product.imageUrl, fit: BoxFit.cover),
      ),
    );
  }
}

6. 实现购物车页面

dart 复制代码
class CartScreen extends StatelessWidget {
  static const routeName = '/cart';

  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<Cart>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Your Cart'),
      ),
      body: Column(
        children: [
          Card(
            margin: const EdgeInsets.all(15),
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text('Total', style: TextStyle(fontSize: 20)),
                  const Spacer(),
                  Chip(
                    label: Text(
                      '$${cart.totalAmount.toStringAsFixed(2)}',
                      style: TextStyle(
                        color: Theme.of(context).primaryTextTheme.titleLarge?.color,
                      ),
                    ),
                    backgroundColor: Theme.of(context).primaryColor,
                  ),
                  TextButton(
                    onPressed: () {
                      // 这里可以添加结算逻辑
                    },
                    child: const Text('ORDER NOW'),
                  )
                ],
              ),
            ),
          ),
          const SizedBox(height: 10),
          Expanded(
            child: ListView.builder(
              itemCount: cart.items.length,
              itemBuilder: (ctx, i) {
                final cartItem = cart.items.values.toList()[i];
                return ListTile(
                  leading: CircleAvatar(
                    child: FittedBox(
                      child: Text('$${cartItem.price}'),
                    ),
                  ),
                  title: Text(cartItem.name),
                  subtitle: Text('Total: $${(cartItem.price * cartItem.quantity).toStringAsFixed(2)}'),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      IconButton(
                        icon: const Icon(Icons.remove),
                        onPressed: () {
                          cart.removeSingleItem(cartItem.productId);
                        },
                      ),
                      Text('${cartItem.quantity}'),
                      IconButton(
                        icon: const Icon(Icons.add),
                        onPressed: () {
                          // 这里可以添加增加商品数量的逻辑
                          // 简化示例中暂不实现
                        },
                      ),
                    ],
                  ),
                );
              },
            ),
          )
        ],
      ),
    );
  }
}

六、Provider 高级用法

1. Selector 优化重建

SelectorConsumer 的进阶版本,它可以根据指定的条件决定是否重建,进一步优化性能:

dart 复制代码
Selector<Counter, int>(
  // 选择需要监听的状态
  selector: (context, counter) => counter.count,
  // 只有当选中的状态变化时才会重建
  builder: (context, count, child) {
    return Text('Count: $count');
  },
)

Selector 接收两个泛型参数:第一个是 ChangeNotifier 类型,第二个是需要监听的状态类型。只有当 selector 方法返回的值发生变化时,builder 才会被调用。

2. 状态持久化

结合 shared_preferences 或 hive 等本地存储库,可以实现状态的持久化:

dart 复制代码
class Counter with ChangeNotifier {
  int _count = 0;
  final SharedPreferences _prefs;

  Counter(this._prefs) {
    // 从本地存储加载状态
    _count = _prefs.getInt('count') ?? 0;
  }

  int get count => _count;

  void increment() {
    _count++;
    // 保存状态到本地存储
    _prefs.setInt('count', _count);
    notifyListeners();
  }
}

// 提供带持久化的状态
ChangeNotifierProvider(
  create: (context) => Counter(SharedPreferences.getInstance()),
  child: MyApp(),
)

3. 状态封装与业务逻辑分离

对于复杂应用,建议将业务逻辑与状态管理分离,保持 ChangeNotifier 的简洁:

dart 复制代码
// 业务逻辑层
class CartService {
  Future<void> addToCart(Product product) async {
    // 处理添加到购物车的业务逻辑,如网络请求等
    await Future.delayed(const Duration(milliseconds: 300));
  }
}

// 状态管理层
class Cart with ChangeNotifier {
  final CartService _cartService;
  final List<CartItem> _items = [];

  Cart(this._cartService);

  List<CartItem> get items => [..._items];

  Future<void> addItem(Product product) async {
    try {
      // 调用业务逻辑
      await _cartService.addToCart(product);
      // 更新状态
      _items.add(CartItem(...));
      notifyListeners();
    } catch (e) {
      // 处理错误
      rethrow;
    }
  }
}

七、Provider 使用最佳实践

  1. 状态粒度适中:避免创建过大的状态类,应根据功能模块拆分状态
  2. 最小重建原则 :使用 ConsumerSelector 限制重建范围,避免不必要的重建
  3. 单一职责:每个状态类应只负责管理相关的一组状态,遵循单一职责原则
  4. 不可变数据:在状态类中,对于复杂数据结构,建议使用不可变对象,通过替换整个对象来更新状态
  5. 避免在 build 方法中创建状态 :确保 ChangeNotifier 实例的创建在 create 方法中,而不是在 build 方法中
  6. 清理资源 :如果状态类中使用了需要手动释放的资源(如定时器),应在 dispose 方法中清理
dart 复制代码
class TimerModel with ChangeNotifier {
  late Timer _timer;
  int _seconds = 0;

  TimerModel() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _seconds++;
      notifyListeners();
    });
  }

  int get seconds => _seconds;

  @override
  void dispose() {
    // 清理资源
    _timer.cancel();
    super.dispose();
  }
}
相关推荐
阿华的代码王国1 天前
【Android】Retrofit2发起GET请求 && POST请求
android·retrofit2
私人珍藏库1 天前
[Android] 京墨 v1.15.2 —— 古诗词文、汉语字典、黄历等查询阅读学习宝典(可离线)
android·学习·安卓
阿华的代码王国1 天前
【Android】JSONObject和Gson的使用
android·java·json·gson·jsonobject
weixin_588531151 天前
android studio 同步慢问题解决
android·ide·android studio
AI2中文网1 天前
别再说AppInventor2只能开发安卓了!苹果iOS现已支持!
android·ios·跨平台·苹果·appstore·app inventor 2·appinventor
咕噜签名分发冰淇淋1 天前
苹果ipa应用安装包ios系统闪退问题
macos·ios·cocoa
xiayiye51 天前
Android原生HttpURLConnection上传图片方案
android·android原生方案上传图片·httpurl上传图片·android原生api上传
wsxlgg1 天前
IOS打包上传 出现 You do not have required contracts to perform an operation 的解决办法
ios
fatiaozhang95271 天前
晶晨线刷工具下载及易错点说明:生成工作流程XML失败
android·xml·网络·电视盒子·刷机固件·机顶盒刷机
CYRUS_STUDIO1 天前
Frida + FART 联手:解锁更强大的 Android 脱壳新姿势
android·操作系统·逆向