必看,使用Provider优雅解决Riverpod的参数依赖

本文探讨了状态管理框架Riverpod、Getx和Bloc在不同业务场景下的适用性,强调了Riverpod在复杂参数传递和组件化方面的挑战。作者提出结合Provider和Riverpod可能的解决方案,以及在实际项目中的应用场景和优化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇文章详细说明了状态管理在开发中的位置和所依赖的基础方法,帧与帧之间的变化是对应状态变化的体现,但每个框架都有其侧重点,Getx侧重简单,简单的页面,简单的状态管理,相对应的是复杂参数, 以及依赖传递时非常臃肿,需要使用很多Listen来同步不同的状态。Bloc有完整的filter状态转移的监听,非常适合编辑器等复杂状态,例如回退操作,这两者一个适合简单业务,一个适合复杂协同业务,大多数项目都没有那么极端,所以Flutter官方推荐了Riverpod作为首推的状态管理工具。

具体对比请移步 一篇文章,告别Flutter状态管理争论,问题和解决

任何工具都有缺点,与此同时,就会有一个或者丑陋,或者优雅的Work around级别的解决方案,riverpod也是,这篇文章试图解决riverpod丑陋的参数传递问题。例如: 如下场景,一个日程任务有多种来源和多个视图才能确定某条任务, 我们假定参数为来源日期来源计划来源看板,那我们定位这一条任务就需要如下代码

/// 声明状态,需要三个入参

class TaskDetail extends _$TaskDetail {
  
  TaskModel build(int taskId, int planId, int viewId) {
    return service.query(taskId, planId, viewId);
  }
}
/// 使用需要明确的三个参数
class TaskDetailScreen extends HookConsumerWidget {
  final int taskId;
  final int planId;
  final int viewId;

  const TaskDetailScreen({
    super.key,
    required this.taskId,
    required this.planId,
    required this.viewId,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    final taskModel = ref.watch(taskDetailProvider(taskId, planId, viewId));
    return Container();
  }
}

目前为止这段代码没有体现出任何的缺点,反而做到了状态的声明使用的分离, 对于简单到中等复杂页面,这非常友好,如果我们选择使用非组件化,将所有代码写到这个TaskDetailScreen那将没有任何问题。因为不涉及参数传递或者指针(notifier)传递。但实际复杂的项目中,通常可重用可阅读也是很重要的指标,这个时候我们不得不考虑使用组件来提升这两个属性。例如:我们有一个富文本组件, 如果我们只有简单的交互,我们可以通过传递taskModel或者增加Callback(String)等方法,与provider进行交互,但这通常会写成如下代码。

/// 需要透传参数或者notifer, 或者抛出Callback
class RichEditor extends ConsumerWidget {
  final int taskId;
  final int planId;
  final int viewId;

  const RichEditor({
    super.key,
    required this.taskId,
    required this.planId,
    required this.viewId,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    return GestureDetector(
      onTap: () => ref.read(taskDetailProvider(taskId, planId, viewId).notifier).saveContent('content'),
      child: Text('editor'),
    );
  }
}

这是非常丑陋的,虽然这种情况在使用Bloc时会被非常优雅的解决,但也可以通过一些结合来尝试解决中等复杂难度的场景。

分析痛点和难点

riverpod在这个场景下的痛点时需要透传参数,且组件化时非常丑陋,容易出错,那他的难点在于什么?在于全局的Scope, 所有全局的管理,例如Getx等都会面临多个层级重复页面,多参数的在同一个路由栈这样常见的问题,或者同时显示多个相同组件(同一个provider)。难点在于无法隔离,也就是跟Context关联,局部状态。

找到难点,我们就可以从这个点出发去尝试解决这个问题,或者是这种特定用例下的问题,在不脱离riverpod的情况下,我们的直观选择是在局部使用ProviderScope, 例如:

 ProviderScope(overrides: [
      sharedPrefsProvider.overrideWithValue(sharedPrefs),
    ], child: const TaskDetailScreen()),

这是一种方案,但这种不符合riverpod的最佳实践,也容易造成状态管理的混乱,第二种方案是将参数使用其它方式进行Context相关的关联,比如通过自定义的InheritedWidget 这种方案类似于ThemeData的局部化处理,这种方案理解简单,但需要不同的页面和不同的Provider定制化, 第二种方案明显也丑陋的,虽然解决了部分问题, 但需要引入自定义的Scope。

组合Hook?

我们知道Riverpod是状态分离的,也就是声明和管理使用状态是完全分离的,所以一个简单好用的界面内状态就可以极大简化这种类型的状态处理。所以,Flutter Hook就完美的补充了这部分。例如

final calendarFormat = useState(CalendarFormat.week);

TableCalendar(
          calendarFormat: calendarFormat.value,
          onFormatChanged: (format) {
            if (format != calendarFormat.value) {
              calendarFormat.value = format;
            }
          },
        ),

这里状态只和页面有关,声明使用修改 都在一个build函数中完成,所以使用注解riverpod是有一点冗余的。本以为可以像React一样,子组件可以跨级获取父组件的状态,后来发现flutter_hook并不是如此,hook和riverpod结合时,hook更像是一个完全的局部管理,并没有Recact Scope这种概念,跨级传递状态。

组合Bloc ?

Bloc是重量级的,声明需要BlocProvider, 消费需要使用BlocConsumer<BlocA, BlocAState>, 这也有点丑陋,虽然我们避免了每一个需要复杂参数都需要声明一个单独的InheritedWidget, 但同样多了很多模版代码。如果是这样的组合,加重了页面的复杂性以及阅读理解难度, 不如直接使用Bloc。

反思, 是不是违背了设计的初衷?

当笔者处处碰壁的时候,想起了之前为了解决打点参数透传而写的一个库data_trakcer, 似乎两者是同样的问题,但不同于打点的简单字段,这里需要明确的状态通知程序。 回顾响应式的设计原则 数据向下传递,操作向上传递, 似乎认为难点或者痛点其实本不应该被关注, 因为按照设计原则,一切都是合理的,我们必须对每个有操作的组件回调到声明notifer进行调用,或者将参数notifer进行一级一级的传递。

应该如何是好?

上述思考的过程,让笔者重新梳理了主流的一些状态管理,但还有很多是我不曾使用和了解的,笔者也不确定是否有其他方案解决了我所头疼的问题,也或许根本不是问题。当我在看Bloc Flutter时,笔者发现,Bloc实现局部Scope的原理其实底层是Provider。这个被遗忘的基础状态管理

static T of<T extends StateStreamableSource<Object?>>(
    BuildContext context, {
    bool listen = false,
  }) {
    try {
      return Provider.of<T>(context, listen: listen);
    } on ProviderNotFoundException catch (e) {
    }
  }

所以,是否可以使用Provider + Rivderpod解决我所遇到的问题?如下代码:

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:https_sync_client/domain/task_provider.dart';
import 'package:provider/provider.dart' as provider;

class TaskDetailScreen extends HookConsumerWidget {
  final int taskId;
  final int planId;
  final int viewId;

  const TaskDetailScreen({
    super.key,
    required this.taskId,
    required this.planId,
    required this.viewId,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    final taskDetailNotifier =
        ref.watch(taskDetailProvider(taskId, planId, viewId).notifier);
    return provider.Provider(
      create: (context) => taskDetailNotifier,
      child: const RichEditor(),
    );
  }
}

class RichEditor extends HookConsumerWidget {
  const RichEditor({
    super.key,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    return GestureDetector(
      onTap: () {
        context.read<TaskDetail>().update(content: "");
      },
      child: Center(
        child: Container(
          color: Colors.amber,
          width: 100,
          height: 100,
        ),
      ),
    );
  }
}

这段代码似乎成功了,又似乎解决了我的痛点。成本似乎只有provider.Provider一个容器和context.read。能不能真的实现痛点,希望各位自己验证一下,实践才是检验真理的唯一标准, 笔者也不确定这么操作是不是符合最佳实践,但在笔者工作当中两个场景是非常令人头痛的,一个打点重运营类项目,经常需要按钮级别的打点需求, 第二是,业务复杂的组件且会堆叠的情况。

总结

遇到问题,解决问题是技术人的一个思考准则,当我们遇到丑陋代码时,总可以找到合适的姿势去改变一点,使之优雅好用一点,世界上没有银弹, 但软件开发领域因为不断的进步,也在逐渐的越来越好(至少不想回去做Android原生)。可以把疑问放到评论区,一起讨论。

<think>我们被要求提供关于在Flutter使用Riverpod进行状态管理的完整解决方案,包括案例详解。根据引用内容,Riverpod是一个强大的状态管理库,具有不可变性、类型安全性、无需BuildContext的访问等优点。我们将从基础开始,逐步构建一个完整的示例。 ### 步骤概述 1. **添加依赖**:在`pubspec.yaml`中添加要的依赖。 2. **基本概念**:介绍Riverpod的核心概念(Provider, Consumer, 等)。 3. **状态管理**:展示如何创建和管理状态。 4. **状态监听与更新**:如何监听状态变化并更新UI。 5. **高级用法**:包括异步操作、状态组合等。 6. **案例实现**:一个简单的计数器应用和一个更复杂的Todo应用。 --- ### 1. 添加依赖 根据引用[3][5],在`pubspec.yaml`中添加依赖: ```yaml dependencies: flutter: sdk: flutter flutter_riverpod: ^2.5.1 riverpod_annotation: ^2.3.5 dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.4.8 riverpod_generator: ^2.4.0 custom_lint: ^0.6.4 riverpod_lint: ^0.6.4 ``` 运行`flutter pub get`安装依赖。 --- ### 2. Riverpod核心概念 - **Provider**:是Riverpod的核心,用于暴露状态和逻辑。它可以是简单的值,也可以是复杂的状态管理。 - **ConsumerWidget**:代替`StatelessWidget`,用于读取Provider。 - **Consumer**:在Widget树中局部订阅Provider。 - **StateProvider**:用于管理可变状态(如计数器)。 - **FutureProvider/StreamProvider**:用于异步操作。 --- ### 3. 状态管理基础 #### 3.1 创建Provider 使用`@riverpod`注解创建Provider。例如,创建一个计数器Provider: ```dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter_provider.g.dart'; @riverpod class Counter extends _$Counter { @override int build() => 0; // 初始状态 void increment() => state++; void decrement() => state--; } ``` 运行`flutter pub run build_runner watch`生成代码(`counter_provider.g.dart`)。 #### 3.2 在UI中使用 使用`ConsumerWidget`或`Consumer`来读取状态: ```dart class CounterPage extends ConsumerWidget { const CounterPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final counter = ref.watch(counterProvider); return Scaffold( body: Center( child: Text('Count: $counter'), ), floatingActionButton: FloatingActionButton( onPressed: () => ref.read(counterProvider.notifier).increment(), child: const Icon(Icons.add), ), ); } } ``` 注意:`ref.watch`用于监听状态变化并重建UI,`ref.read`用于在不监听的情况下读取状态(如按钮点击)。 --- ### 4. 异步操作示例(FutureProvider) 假设我们需要从网络加载用户数据: ```dart @riverpod Future<String> fetchUser(FetchUserRef ref) async { final response = await http.get(Uri.parse('https://api.example.com/user')); return response.body; } ``` 在UI中处理加载状态: ```dart class UserPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final userAsync = ref.watch(fetchUserProvider); return userAsync.when( loading: () => const CircularProgressIndicator(), error: (err, stack) => Text('Error: $err'), data: (user) => Text('User: $user'), ); } } ``` --- ### 5. 复杂案例:Todo应用 我们创建一个Todo应用,包含以下功能: - 添加待办事项 - 标记完成 - 过滤(全部、未完成、已完成) #### 5.1 定义模型 ```dart class Todo { final String id; final String description; bool completed; Todo({required this.id, required this.description, this.completed = false}); } ``` #### 5.2 创建TodoList Provider ```dart @riverpod class TodoList extends _$TodoList { @override List<Todo> build() => []; void add(String description) { state = [ ...state, Todo(id: DateTime.now().toString(), description: description), ]; } void toggle(String id) { state = [ for (final todo in state) if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo, ]; } } ``` #### 5.3 过滤功能 使用`Provider`创建过滤后的列表: ```dart enum Filter { all, active, completed } @riverpod class FilterType extends _$FilterType { @override Filter build() => Filter.all; } @riverpod List<Todo> filteredTodos(FilteredTodosRef ref) { final filter = ref.watch(filterTypeProvider); final todos = ref.watch(todoListProvider); switch (filter) { case Filter.all: return todos; case Filter.active: return todos.where((todo) => !todo.completed).toList(); case Filter.completed: return todos.where((todo) => todo.completed).toList(); } } ``` #### 5.4 UI实现 ```dart class TodoScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final todos = ref.watch(filteredTodosProvider); return Scaffold( appBar: AppBar(title: const Text('Todo')), body: ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return CheckboxListTile( value: todo.completed, onChanged: (value) => ref.read(todoListProvider.notifier).toggle(todo.id), title: Text(todo.description), ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => showDialog(...), // 打开添加对话框 child: const Icon(Icons.add), ), ); } } ``` --- ### 6. 测试与调试 - **测试Provider**:使用`ProviderContainer`进行单元测试。 - **调试**:使用Flutter Inspector查看状态(如引用[2]所示)。 --- ### 总结 Riverpod提供了一种灵活且强大的状态管理方案,适用于各种规模的Flutter应用。通过上述步骤,我们实现了计数器、异步数据加载和Todo应用,展示了Riverpod的核心功能。更多细节可参考官方文档[^1]。 §§相关问题§§ 1. 如何在Riverpod中处理依赖注入? 2. RiverpodProvider库的主要区别是什么? 3. 如何测试Riverpod的异步Provider?</think>### Flutter Riverpod 状态管理使用详解与案例 RiverpodFlutter 最现代化的状态管理解决方案之一,提供类型安全、依赖注入和高效状态管理能力。以下是完整实现方案: --- ### 一、核心概念与优势 1. **Provider 类型**: - `Provider`:基础只读值 - `StateProvider`:可变简单状态 - `StateNotifierProvider`:复杂业务逻辑 - `FutureProvider`:异步操作管理 - `StreamProvider`:流数据处理[^1][^5] 2. **核心优势**: - 🛡️ 编译时类型安全 - 🔗 无 `BuildContext` 依赖 - ♻️ 自动处理依赖关系 - 🧪 易测试性 - 🧩 可组合性[^1][^4] --- ### 二、基础配置 #### 1. 添加依赖 (`pubspec.yaml`) ```yaml dependencies: flutter_riverpod: ^2.5.1 riverpod_annotation: ^2.3.5 dev_dependencies: build_runner: ^2.4.8 riverpod_generator: ^2.4.0 custom_lint: ^0.6.4 riverpod_lint: ^0.6.4 ``` #### 2. 启用代码生成 ```bash flutter pub run build_runner watch ``` --- ### 三、完整案例:用户管理系统 #### 1. 定义数据模型 ```dart // user_model.dart class User { final String id; final String name; final String email; User({required this.id, required this.name, required this.email}); } ``` #### 2. 创建状态管理 (Riverpod Generator) ```dart // user_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user_provider.g.dart'; @riverpod class UserManager extends _$UserManager { @override List<User> build() => []; // 初始状态 // 添加用户 void addUser(User user) { state = [...state, user]; } // 删除用户 void removeUser(String userId) { state = state.where((user) => user.id != userId).toList(); } } ``` #### 3. 创建 UI 组件 ```dart // user_list_screen.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; class UserListScreen extends ConsumerWidget { const UserListScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final users = ref.watch(userManagerProvider); return Scaffold( appBar: AppBar(title: const Text('用户管理')), body: ListView.builder( itemCount: users.length, itemBuilder: (_, index) => ListTile( title: Text(users[index].name), subtitle: Text(users[index].email), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () => ref .read(userManagerProvider.notifier) .removeUser(users[index].id), ), ), ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddUserDialog(context, ref), child: const Icon(Icons.add), ), ); } void _showAddUserDialog(BuildContext context, WidgetRef ref) { // 对话框实现... } } ``` #### 4. 应用入口配置 ```dart // main.dart void main() { runApp( ProviderScope( // 包裹整个应用 child: MaterialApp( home: UserListScreen(), ), ), ); } ``` --- ### 四、高级用法 #### 1. 异步数据处理 (FutureProvider) ```dart @riverpod Future<List<User>> fetchUsers(FetchUsersRef ref) async { final response = await http.get(Uri.parse('https://api.example.com/users')); return (json.decode(response.body) as List) .map((data) => User.fromJson(data)) .toList(); } // 在UI中使用 ref.watch(fetchUsersProvider).when( data: (users) => ListView(...), loading: () => CircularProgressIndicator(), error: (err, _) => Text('Error: $err'), ); ``` #### 2. 状态组合 (Provider 依赖) ```dart @riverpod int activeUserCount(ActiveUserCountRef ref) { final users = ref.watch(userManagerProvider); return users.where((user) => user.isActive).length; } ``` #### 3. 自动保持状态 ```dart class UserDetailScreen extends ConsumerStatefulWidget { const UserDetailScreen({super.key}); @override ConsumerState<UserDetailScreen> createState() => _UserDetailScreenState(); } class _UserDetailScreenState extends ConsumerState<UserDetailScreen> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; // 保持状态 @override Widget build(BuildContext context) { super.build(context); // 构建UI... } } ``` --- ### 五、调试技巧 1. **使用 Flutter Inspector**: - 在调试模式下查看 Provider 状态树[^2] - 检查状态依赖关系 2. **日志监控**: ```dart ref.listen(userManagerProvider, (_, next) { debugPrint('用户状态变化: ${next.length}个用户'); }); ``` --- ### 六、最佳实践 1. **业务逻辑分离**:状态管理类只处理状态变更,UI 只负责展示 2. **最小化重建**:使用 `select` 精确监听部分状态 ```dart final userName = ref.watch(userManagerProvider.select((users) => users[0].name)); ``` 3. **依赖注入**:通过 Provider 覆盖实现测试替身 4. **状态持久化**:结合 `shared_preferences` 或 `hive` 实现本地存储 通过以上方案,您可以构建高效、可维护的 Flutter 应用。完整示例可参考 Riverpod 官方文档[^1]和社区案例[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值