JHHK

欢迎来到我的个人网站
行者常至 为者常成

flutter_riverpod

目录

介绍

一、区分两个包
riverpod:纯 Dart 版本,无 Flutter 依赖,可在 Dart 命令行。
flutter_riverpod:基于 riverpod,绑定 Flutter,提供 Widget 扩展,实际项目绝大多数用这个。
flutter_riverpod 是 三方为Flutter提供的状态管理库,是 Provider 的升级版

二、优缺点
优点
脱离 BuildContext,不依赖Widget,逻辑与UI分离。
单元测试友好,适合中大型项目。
provider 之间可以灵活组合复用。
自动内存回收,减少内存泄漏。

缺点
学习成本更高,概念多
简单页面使用会增加样板代码

三核心概念

1、ProviderScope
状态根容器,整个 Riverpod 的入口
作用:存放所有 provider 的实例、缓存、状态;替代旧 provider 把状态挂在 Widget 树(InheritedWidget)的方式。 使用:必须包裹 MaterialApp;支持嵌套,实现局部状态隔离(弹窗、子页面独立状态)。
注意:ProviderScope 以外无法访问任何 provider。

2、Provider(各类状态提供者)
Provider 是状态的定义声明,不是状态本身。

类型 用途
Provider 只读静态数据,工具实例、常量,不会变化
StateProvider 简单可变状态,基础类型 bool、int、String,简单表单
NotifierProvider<Notifier,T> 复杂同步业务逻辑,封装修改状态的方法
AsyncNotifierProvider<AsyncNotifier,T> ✅最常用,带异步逻辑(网络请求),返回AsyncValue
FutureProvider 封装 Future,自动处理加载 / 错误,简单异步
StreamProvider 监听 Stream 数据流

3、Notifier / AsyncNotifier
业务逻辑封装载体,把状态修改逻辑从 Widget 抽离出去。

4、 Consumer / ConsumerStatefulWidget 获取 ref 的两种 UI 组件

5、 Ref
操作状态的核心句柄,重中之重。
不再依赖 BuildContext,所有读写、监听、失效都通过 ref。

基本使用

一、最简单的 Provider:只读数据

final nameProvider = Provider<String>((ref) {
  return 'Flutter';
});
class HomePage extends ConsumerWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch(nameProvider)对应以前 Provider 的:context.watch<NameNotifier>()
    // Riverpod 最大的变化之一是:Provider 不再必须绑定在 Widget Tree 上。
    final name = ref.watch(nameProvider);

    return Text(name);
  }
}

二、StateProvider:简单状态

final searchProvider = StateProvider<String>((ref) {
  return '';
});
class ProductPage extends ConsumerWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final keyword = ref.watch(searchProvider);

    return Column(
      children: [
        Text('搜索:$keyword'),

        ElevatedButton(
          onPressed: () {
            ref.read(searchProvider.notifier).state = 'iPhone';
          },
          child: const Text('搜索 iPhone'),
        ),
      ],
    );
  }
}

三、NotifierProvider更推荐的状态管理方式

class CartNotifier extends Notifier<List<String>> {

  @override
  List<String> build() {
    return [];
  }

  void add(String product) {
    state = [
      ...state,
      product,
    ];
  }

  void remove(String product) {
    state = [
      ...state.where((item) => item != product),
    ];
  }
}
final cartProvider =
    NotifierProvider<CartNotifier, List<String>>(
  CartNotifier.new,
);
class CartPage extends ConsumerWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    // 状态变化 → 通知依赖者 → Widget rebuild
    final cart = ref.watch(cartProvider);

    return Column(
      children: [
        Text('商品数量:${cart.length}'),

        ElevatedButton(
          onPressed: () {
            ref
                .read(cartProvider.notifier)
                .add('iPhone');
          },
          child: const Text('加入购物车'),
        ),
      ],
    );
  }
}

四、AsyncNotifierProvider:网络请求

不带参数的网络请求

class ProductListNotifier extends AsyncNotifier<List<Product>> {
  bool mockNetworkError = true;

  Future<List<Product>> fetchProducts() async {

    await Future.delayed(
      const Duration(seconds: 2),
    );

    // 模拟网络异常
    if (mockNetworkError) {
      // 可以抛 Exception,也可以抛自定义异常对象
      throw Exception("网络请求失败,请检查网络连接");
    }

    return [
      Product(id: 1, name: 'iPhone'),
      Product(id: 2, name: 'MacBook'),
      Product(id: 3, name: 'iPad'),
    ];
  }


  
  @override // 初始化过程中会自动调用
  Future<List<Product>> build() async {
    // 在初始化过程中,如果build方法中有错误抛出,会走到AsyncValue的error回调
    return fetchProducts();
  }



  Future<void> reload() async {

    mockNetworkError = false;

    // 初始化状态为loading
    state = const AsyncLoading();

    // try {
    //   final result = await fetchProducts();
    //   state = AsyncData(result);
    // } catch (e, stackTrace) {
    //   state = AsyncError(e, stackTrace);
    // }

    // 这样写相当于,上边的try catch 逻辑
    state = await AsyncValue.guard(
          () => fetchProducts(),
    );

    /*
    * AsyncLoading会触发RiverpodBody.build()调用
    * 然后AsyncValue.when(loading,error,data)方法调用
    * 根据state不同,调用传入的loading方法或者error方法或者data方法
    *
    * */
  }
}
final productListProvider = AsyncNotifierProvider.autoDispose<
    ProductListNotifier, 
    List<Product>
>(ProductListNotifier.new);
class RiverpodBody extends ConsumerWidget {
  const RiverpodBody({super.key});

  @override
  Widget build( BuildContext context, WidgetRef ref) {

    // xy: ref.watch() -> dependence(依赖绑定) -> A方法 ->  ProductListNotifier.build()
    // xy:猜测A方法的实现逻辑大概如下:
    // // 1、初始化状态为loading
    // state = const AsyncLoading();
    // // 2、网络请求
    // try {
    //   final result = await ProductListNotifier.build();
    //   // 状态修改为成功
    //   state = AsyncData(result);
    // } catch (e, stackTrace) {
    //   // 状态修改为报错。此处说明初始化时在build中抛出的错误会被捕获,并更新状态
    //   state = AsyncError(e, stackTrace);
    // }

    final AsyncValue<List<Product>> products = ref.watch(productListProvider);

    // AsyncValue<List<Product>>
    // │
    // ├── loading
    // ├── error
    // └── data

    return products.when(
      loading: () {
        return const Center(
          child: CircularProgressIndicator(),
        );
      },

      error: (error, stack) {
        return Center(
          child: Column(children: [
            Text('加载失败:$error'),
            TextButton(
                onPressed: (){
                  ref.read(productListProvider.notifier)
                      .reload();
                },
                child: const Text("点击刷新")
            )
          ],)
        );
      },

      data: (list) {
        return ListView.builder(
            itemCount: list.length + 1,
            itemBuilder: (context, index) {
              if(index < list.length) {
                return ListTile(
                  title: Text(list[index].name),
                );
              } else {
                return TextButton(
                    onPressed: (){
                      ref.read(productListProvider.notifier)
                          .reload();
                    },
                    child: const Text("点击刷新")
                );
              }
            });
      },
    );
  }
}

flutter_riverpod其实结构是这三部分:asyncNotifier + asyncNotifierProvider + consumer(ref)
老的provider结构也是这三部分:changeNotifier + changeNotifierProvider + consumer()
它们不同点是asyncNotifierProvider与widget无关,changeNotifierProvider是widget

带参数的网络请求

/// 参数通过**构造函数**注入(而非 build 的形参),
/// 在 build 内部直接读取 Notifier 实例字段即可。
class ProductDetailNotifier extends AsyncNotifier<Product> {
  ProductDetailNotifier(this.productId);

  int productId;

  @override
  Future<Product> build() async {
    debugPrint('开始请求商品:$productId');

    await Future.delayed(
      const Duration(seconds: 2),
    );

    return Product(
      id: productId,
      name: '商品 $productId',
    );
  }

  Future<void> reload(int productId) async {
    state = const AsyncLoading();
    this.productId = productId;
    state = await AsyncValue.guard((){
      return build();
    });
  }
}
/// AsyncNotifierProvider.family 的 create 函数签名为
/// `NotifierT Function(ArgT arg)`,即接收 family 参数并返回 Notifier 实例。
/// Riverpod 内部会用 family 调用时传入的参数调用此函数,
/// 再由返回的 Notifier 调用无参 build()。
final productDetailProvider = AsyncNotifierProvider.family.autoDispose<
    ProductDetailNotifier,
    Product,
    int
>(ProductDetailNotifier.new);
// 在此处传递实参
// Dart 可调用对象 `call()` 语法基础
// Dart 中,如果一个类实现了 `call` 方法,该类的实例可以直接像函数一样加括号调用。
// productFutureProvider(20) 等价于 productFutureProvider.call(20)
final AsyncValue<Product> product = ref.watch(productDetailProvider(20));

// 注意要想找到之前的notifier,必须productDetailProvider(20)
// 如果写成ref.read(productDetailProvider(30),页面没有反应。
ref.read(productDetailProvider(20).notifier).reload(30);

五、FutureProvider

适应于简单的一次性异步,只读,无手动刷新 / 复杂逻辑 futureProvider 是没有notifier的,所以他没法写复杂逻辑 不带参数

final productFutureProvider = FutureProvider.autoDispose<Product>((ref) async {
  // autoDispose:页面销毁自动释放资源,防止内存泄漏
  // 模拟异步请求:模拟网络接口,2秒延迟,随机抛出错误演示异常场景
  await Future.delayed(const Duration(seconds: 2));

  // 模拟50%概率报错,测试error分支
  final bool mockError = DateTime.now().millisecond % 2 == 0;
  if (mockError) {
    throw Exception("网络请求失败,请稍后重试");
  }

  // 模拟接口返回数据
  return Product(id: 1, name: 'iPhone');
});

带参数

final productFutureProvider = FutureProvider.family.autoDispose<
    Product, 
    int
>((ref, int productId) async {
  await Future.delayed(const Duration(seconds: 2));

  final bool mockError = DateTime.now().millisecond % 2 == 0;
  if (mockError) {
    throw Exception("网络请求失败,请稍后重试");
  }

  return Product(id: productId, name: 'iPhone');
});
// 强制刷新逻辑,带参数和不带参数都用这个
ref.invalidate(productFutureProvider);

// 注意:带参数的刷新逻辑不能写成
ref.invalidate(productFutureProvider(20));

关键配置

一、autoDispose

autoDispose 修饰符控制 Provider 的生命周期:没有监听者时,autoDispose 版本会自动销毁 Notifier + 释放状态;
不带 autoDispose 的会永久缓存状态,常驻内存。

特性 AsyncNotifierProvider.autoDispose AsyncNotifierProvider(不带)
生命周期 当没有任何 ref.watch/ref.listen监听它时,自动 dispose,销毁 Notifier 实例,清空 state 一旦初始化,永久保存在内存,即使页面销毁、没有监听者,状态仍然保留
再次进入页面 重新执行 build(),重新请求接口,数据刷新 复用上次缓存好的旧 state,不会重新请求
资源清理 ref.onDispose 回调会执行,可以取消请求、关闭流、清理定时器 页面退出后资源不会自动清理,容易内存泄漏
适用场景 页面独有的列表、分页、详情页数据(离开页面就丢弃) 全局状态:登录信息、App 全局配置,需要跨页面缓存
内存 省内存,页面销毁释放资源 常驻内存,适合全局共享状态

选型建议
✅ 页面私有列表、分页、详情页、搜索结果 → 优先 autoDispose
✅ 全局状态(登录 token、主题、全局基础字典)→ 不用 autoDispose

二、read / watch / listen

API 作用 触发重建 使用位置 典型场景
ref.read(provider) 获取 provider当前快照,只执行一次 ❌ 不重建 点击回调、函数内部(不要在 build 直接写) 按钮点击触发加载、调用 notifier 方法
ref.watch(provider) 持续订阅 provider,状态一变,重建当前所在 widget ✅ 自动重建 在 build 里 根据状态渲染 UI(加载中、错误、列表数据)
ref.listen(provider, (prev, next)=>{}) 持续订阅 provider,状态变化执行回调 ❌ 不重建当前 在 build 里 捕获状态变化做副作用:弹出提示、跳转页面、打印日志

三、consumer相关

名称 ref 位置 重建范围 适用场景
ConsumerWidget build 入参WidgetRef ref 整个组件全部重建 小型无状态组件、列表 Item
ConsumerStatefulWidget state 内部直接访问 ref 整个 State 组件重建 需要 initState/dispose,同时依赖 riverpod。
比如输入框要维护自己的controller
Consumer builder 参数 仅 Consumer 内部 builder 重建,外层不动 大页面局部 UI 优化,隔离重建范围
selector 配合 watch 只有选中的片段变化才重建 大状态对象,组件只需要其中部分字段

selector 在 flutter_riverpod中怎么做?

final name = ref.watch(
  userProvider.select(
    (user) => user.name,
  ),
);

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.