Flutter高效开发利器:Riverpod框架简介及实践指南

本文同步发布于公众号:移动开发那些事 Flutter高效开发利器:Riverpod框架简介及实践指南

1 简介

Riverpod 是一个用于 Flutter 的状态管理库,由 Provider 的作者开发,是对 Provider 的重构和改进,提供一种更灵活,更简洁和高效的状态管理工具,它具有以下几个关键特性:

  • 类型安全: 在运行时和编译时都能获得类型检查,减少运行时错误,提高代码的可靠性和可维护性;
  • 不依赖 BuildContext:允许在不依赖 BuildContext 的情况下访问和管理状态,简化了许多常见的 Flutter 状态管理问题,使代码更加清晰和易于维护。
  • 自动化状态清理:状态会在不再需要时自动清理,避免内存泄漏,提高了应用的性能和稳定性;
  • 支持 "Scoped" 状态 提供了更强大的 scoping 机制,可以在不同的widget 树中有选择地管理和隔离状态,有助于更细粒度的状态管理和性能优化。

2 基础使用

ProviderRiverpod 库的核心概念,主要负责管理和更新状态,可以将 Provider 看作是一个数据源,它负责提供数据,并在数据发生变化时通知相关的组件。它有以下几种常用的Provider

  • Provider:基础只读值;
  • FutureProvider : 异步操作封装
  • StreamProvider : 流式数据处理
  • NotifierProvider: 提供一种更灵活的方式来管理状态和业务逻辑,支持任何类型的 "Notifier"
  • AsyncNotifierProvider : 专门用于管理异步操作的状态,如网络请求,它提供了一个结构化的方法来处理异步数据的加载、成功、错误和状态更新
  • 其他

使用之前,我们首先要在pubspec.yaml文件中添加对应的依赖:

yaml 复制代码
dependencies:
  flutter_riverpod: ^2.3.6
  # 可选的,这个主要是用来使用注解,自动生成对应的provider的
  riverpod_annotation: ^2.1.1

 dev_dependencies:
  # 与注解相结合,自动生成dart文件
  build_runner:
  # Riverpod代码生成器
  riverpod_generator: ^2.4.0

一个基础使用Riverpod库的步骤为:

  • 创建一个Provider: 创建一个Provider,去观察某一个值,并在值有变化时去通知监听者;
  • 使用Provider : 使用Provider的前提是使用了ProviderScope来包裹对应的widget;
  • 监听状态: 在对应的widget(这个widget需要继承自ConsumerWidget)中使用ref.watch方法来监听对应的值;
  • 更新状态: 在对应的widget中使用ref.read方法获取对应的监听器来修改状态值;

2.1 简单使用

最简单的使用Riverpod的例子为

scala 复制代码
// 步骤1 :通过StateProvider创建一个provider,监听一个int值,这个provider会对这个值的状态进行监听管理
final counterProvider = StateProvider<int>((ref) => 0);
// 步骤2 :用ProviderScope包裹对应的app
void main() {
	runApp (const ProviderScope(child:CounterPage()));
}
// 步骤3 继承 ConsumerWidget 编写widget,用于监听provider的状态
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
  	// 步骤4 监听前面定义的 counterProvider
    final count = ref.watch(counterProvider);
    return Scaffold(
    	// 只要counterProvider的状态有变化,这个text就会自动刷新
      body: Text('Count: $count'),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
        	// 步骤5(按需) 通过read方法改变其状态
        	ref.read(counterProvider.notifier).state++,
        }
        child: Icon(Icons.add),
      ),
    );
  }
}

3 高级使用

3.1 组合状态

在实际开发中,应用的状态管理往往会涉及到多个相互关联的状态和复杂的业务逻辑,可以多个独立的状态类组合到一个类中,从而实现某个功能统一的管理

scala 复制代码
// 定义多个状态
final counterProvider = StateProvider((ref) => 0);
final nameProvider = StateProvider((ref) => "");

// 创建组合状态
class CombinedState {
  final int counter;
  final String name;
  
  CombinedState(this.counter, this.name);
}

// 创建组合的Provider
final combinedProvider = Provider((ref) {
  final counter = ref.watch(counterProvider);
  final name = ref.watch(nameProvider);
  return CombinedState(counter, name);
});

// 使用组合状态
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(combinedProvider);
    
    return Text('${state.counter}, ${state.name}');
  }
}

3.2 特定状态

若你想仅关注某个状态里的特定值,可借助select 功能达成此目的。这种方式十分高效,因为它能让组件只在特定值发生变化时才进行重建。

scala 复制代码
// StateProvider也可用StateNotifierProvider替代
// 假设我们有一个 User 状态
final userProvider = StateProvider((ref) => User(name: "John", age: 30));

class User {
  final String name;
  final int age;
  User({required this.name, required this.age});
}

// 只监听 name 字段
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 使用 select 只监听 name 字段的变化
    final name = ref.watch(userProvider.select((user) => user.name));
    
    return Text('Name: $name'); // 仅当 name 变化时重建
  }
}

如果要用select处理比较复杂的状态,如状态是包括列表的,或者是需要进行计算转换的,关键在于高效提取所需数据并确保正确的状态比较,这里有几点经验可参考:

  • 针对状态包含列表,可通过 mapwhere 过滤后提取数据:
javascript 复制代码
ref.watch(
      todosProvider.select((todos) => todos.where((todo) => !todo.completed).length)
  • 避免返回对象,若要提取多个对象,优先返回一个元组而非新对象:
scss 复制代码
   final (name, age) = ref.watch(
      userProvider.select((user) => (user.profile.name, user.profile.age)),
    );

3.3 异步请求

使用FutureProvider或者StreamProvider 可以很容易构建出响应式的数据请求,比如我们需要从另一个服务中获取到用户id后,再去请求这个用户的数据,那 这里的功能利用FutureProvider 可以很简洁地写出来:

dart 复制代码
// 基于用户 ID 获取用户详情
// userIdProvider 用于 获取用户的id,这里provider可按需选用任意一个
final userIdProvider = StateProvider((ref) => 1);

// userDetailsProvider 用于获取用户的详情,这里接受一个userId的参数
final userDetailsProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
  // 可监听其他状态(如网络状态)
  final networkStatus = ref.watch(networkStatusProvider);
  if (!networkStatus) throw Exception('No internet');
  
  // 使用 userId 发起请求
  return ref.watch(apiServiceProvider).getUser(userId);
});

// 使用方法
class UserDetailsWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 先通过 userIdProvider 获取到用户id
    final userId = ref.watch(userIdProvider);
    // 再通过用户id,去获取用户详情 (这里也可以用前面组合的方式来写)
    final userAsync = ref.watch(userDetailsProvider(userId));
    
    return userAsync.when(
      data: (user) => Text('User: ${user.name}'),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

FutureProvider.autoDispose.family:

  • autoDispose : 在没有组件监听时,会自动取消异步操作并释放资源,再次监听时,会重新触发操作
  • family : 接收参数,动态生成不同的异步操作

而且FutureProvider会自动处理异步操作的不同状态(加载中、数据返回、出错),通过when方法可以根据不同状态展示相应的 UI。

3.4 其他

如果觉得手动写Provider的方式比较繁锁,Riverpod库也提供一注解的方式来简化,使用注解的话,需要增加一个依赖: riverpod_generator: ^2.4.0

typescript 复制代码
// 生成FutureProvider
@riverpod
Future<User> fetchUser(FetchUserRef ref, String userId) async {
  // 可监听其他 Provider
  final apiClient = ref.watch(apiClientProvider);
  
  // 执行异步操作
  return apiClient.fetchUser(userId);
}

// 生成StreamProvider
@riverpod
Stream<int> counterStream(CounterStreamRef ref) {
  return Stream.periodic(Duration(seconds: 1), (i) => i);
}

写完后,需要在命令行执行一个命令:dart run build_runner build,然后就能很方便使用对应的Provider了。

4 总结

本文主要探讨了Riverpod框架的一些特点,包括它的的基础和高级使用方式,并希望通过本文能够帮助大家入门Riverpod并在实际开发中充分发挥Riverpod的优势,以一种更简洁,更方便的方式来管理应用的状态。

5 参考

相关推荐
阅文作家助手开发团队_山神4 小时前
第四章(下) Delta 到 HTML 转换:块级与行内样式渲染深度解析
flutter
MaoJiu4 小时前
Flutter造轮子系列:flutter_permission_kit
flutter·swiftui
阅文作家助手开发团队_山神8 小时前
第四章(下):Delta 到 HTML 转换的核心方法解析
flutter
xiaoyan201511 小时前
flutter3.32+deepseek+dio+markdown搭建windows版流式输出AI模板
flutter·openai·deepseek
阅文作家助手开发团队_山神11 小时前
第四章(上):HTML 到 Delta 转换的核心方法解析
flutter
耳東陈12 小时前
Flutter开箱即用一站式解决方案2.0-全局无需Context的Toast
flutter
阅文作家助手开发团队_山神1 天前
第三章: Flutter-quill 数据格式Delta
flutter
阅文作家助手开发团队_山神1 天前
第二章:Document 模块与 DOM 树详解
flutter
程序员老刘1 天前
20%的选择决定80%的成败
flutter·架构·客户端