【Flutter 工程】001-Flutter 状态管理:Riverpod
文章目录
- 【Flutter 工程】001-Flutter 状态管理:Riverpod
- 一、概述
- 1、官方状态管理
- 2、状态管理解决方案
- 3、为什么选择 Riverpod
- Riverpod 官方文档
- Riverpod 提供的几种 Provider
- 二、官方示例
- 1、安装
- 2、官方示例
- 3、代码生成
- 4、官方示例运行结果
- 三、基本使用
- 1、改造 `main.dart`
- 2、创建 `home_page.dart`
- 3、创建 `hello_state.dart`
- 4、运行结果
- 四、使用代码生成
- 1、改造 `hello_state.dart`
- 2、代码生成
- 3、改造 `home_page.dart`
- 4、运行结果
- 5、为什么在Riverpod中使用代码生成
一、概述
1、官方状态管理
状态管理处理应用程序数据流动和 UI 更新的关键概念。在 Flutter 应用程序中,状态管理确保应用程序 UI 和数据保持同步,共享和同步数据,并提供良好的代码结构和可维护性。
Flutter 提供了 StatefulWidget
作为最基本的状态管理方法。有状态组件可以存储和更新自身状态,适用于简单的场景和局部状态。
然而,StatefulWidget
存在以下问题:
- 状态管理复杂性:当组件树庞大且状态需要在多个组件之间共享时,状态管理变得复杂,代码难以理解和维护。
- 性能问题:相比
StatelessWidget
,StatefulWidget
在状态变化时会导致更多组件重建,可能影响应用程序性能,尽管Flutter已经进行了性能优化。 - 生命周期管理复杂性:
StatefulWidget
具有复杂的生命周期,需要处理多个生命周期方法(如initState
、didUpdateWidget
和dispose),导致代码复杂和难以管理。 - 难以测试:由于
StatefulWidget
具有内部状态,编写单元测试和集成测试变得更加困难,可能影响应用程序的质量和可靠性。 - 重用性差:
StatefulWidget
的状态通常与特定实例紧密耦合,降低了组件的可重用性。
2、状态管理解决方案
在 Flutter 中,还有其他的状态管理方法可供选择,以下是一些常见的状态管理方法。
- InheritedWidget 和 InheritedModel:这些是 Flutter 提供的允许状态在组件树中向下传递的特殊类型的组件。它们可以帮助你在应用程序的不同层级之间共享状态。这种方法
对于较小的应用程序或有限的状态共享需求较为合适
。 - Provider: 一个依赖注入和状态管理第三方库,它是在 InheritedWidget 基础上做了封装,有上面组件的能力,但是更简单易用。Provider 可以监听状态变化,并在需要时重新构建关联的组件。这种方法适用于
各种规模的应用程序
,具有良好的可扩展性和灵活性。 - Riverpod: 一个相对较新的状态管理库,类似于 Provider,但提供了更多的功能和改进。Riverpod 允许你创建不可变的、可组合的和可测试的状态管理解决方案。这种方法适用于
需要更高度可控和可测试性的应用程序
。 - BLoC(Business Logic Component) :一种基于响应式编程的状态管理方法。BLoC 将业务逻辑与 UI 分离,使你可以轻松地测试和重用代码。BLoC 通常与 RxDart(一种 Dart 的响应式编程库)一起使用,以提供强大的数据流处理能力。这种方法适用于
需要处理复杂业务逻辑和大量数据流的应用程序
。 - Redux: 一种集中式状态管理库,它将应用程序的状态存储在一个单一的状态树中。Redux 使用纯函数(称为reducers)来处理状态更新,使你可以轻松地跟踪和管理应用程序的状态变化。这种方法适用于
需要严格的状态管理和可预测性的应用程序
。
具体选择什么样的状态管理方法,这取决于你应用程序的需求、复杂性和个人喜好。不同的方法有不同的优缺点,因此在选择状态管理方法时,请务必充分了解每种方法的特点,并权衡其适用性。
3、为什么选择 Riverpod
究其原因,还是 Riverpod 的一些主要特点比较给力,与我们的需求契合,且听我慢慢道来……
- 不可变性。Riverpod 中的状态是不可变的,这意味着状态在更新时会创建一个新的对象,而不是修改现有对象。这有助于减少错误,并使状态更易于理解和跟踪。
- 类型安全。Riverpod 在编译时提供了更强的类型安全性,有助于减少类型错误并提高代码质量。
- 无需 BuildContext。 与 Provider 不同,Riverpod 不依赖于 BuildContext 来访问状态。这使得在组件之外的位置(如函数或类)访问状态变得更加容易,同时提高了可测试性。
- 可组合。Riverpod 允许你组合不同的 Provider 以创建更复杂的状态管理解决方案。这有助于保持代码的模块化和可维护性。
- 易于测试。由于 Riverpod 的状态不依赖于 BuildContext,你可以更轻松地编写单元测试。此外,Riverpod 提供了用于模拟状态和测试的实用工具。
- 家族功能。Riverpod 具有所谓的“家族”功能,允许你根据参数创建多个相同类型的 Provider 实例。这使得在使用相同逻辑但参数不同的多个组件时,可以更好地管理状态。
- 非常灵活。Riverpod 具有很高的灵活性,可以很好地适应不同的应用程序结构和需求。你可以使用 Riverpod 来构建简单的局部状态管理,或者构建复杂的全局状态管理解决方案。
总之,Riverpod 是一个强大的状态管理库,适用于各种规模的 Flutter 应用程序。它提供了不可变性、类型安全性、无需 BuildContext 的访问、可组合性、易于测试和家族功能等多种优点。如果你正在寻找一个现代、灵活且易于使用的状态管理解决方案,Riverpod 是一个值得考虑的选择。
Riverpod 官方文档
https://docs-v2.riverpod.dev/zh-hans/
Riverpod 提供的几种 Provider
二、官方示例
1、安装
flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator
2、官方示例
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.dart';
// 我们创建一个 “provider”,它将用于保存一个值(这里是 “Hello world”)。
// 通过使用一个 provider,我们能够模拟或覆盖被暴露的值。
String helloWorld(HelloWorldRef ref) {
return 'Hello world';
}
void main() {
runApp(
// 为了能让组件读取 provider,我们需要将整个
// 应用都包裹在 “ProviderScope” 组件内。
// 这里也就是存储我们所有 provider 状态的地方。
ProviderScope(
child: MyApp(),
),
);
}
// 扩展来自 Riverpod 的 HookConsumerWidget 而不是 HookWidget
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final String value = ref.watch(helloWorldProvider);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Example')),
body: Center(
// 读取 provider 的值
// 此处为了方便查看,设置了大字体
child: Text(value, style: const TextStyle(fontSize: 40),),
),
),
);
}
}
3、代码生成
# --delete-conflicting-outputs 可选,会在生成代码冲突的时候,删除原来的代码,重新生成
flutter pub run build_runner build --delete-conflicting-outputs
4、官方示例运行结果
三、基本使用
1、改造 main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:study/pages/HomePage.dart';
void main() {
runApp(
// 为了能让组件读取 provider,我们需要将整个
// 应用都包裹在 “ProviderScope” 组件内。
// 这里也就是存储我们所有 provider 状态的地方。
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
2、创建 home_page.dart
/lib/pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../states/hello_state.dart';
class HomePage extends HookConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final hello = ref.watch(helloStateProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: SizedBox(
height: 400,
child: Column(
children: [
// 文本
Text(hello.hello, style: const TextStyle(fontSize: 40),),
// 更新文本
ElevatedButton(
style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
onPressed: () {
ref.read(helloStateProvider.notifier).setHello("文本更新了!");
},
child: const Text('Update'),
),
],
),
)),
);
}
}
3、创建 hello_state.dart
lib/state/hello_state.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HelloState {
final String hello;
HelloState({
this.hello = 'Hello World',
});
}
class HelloStateProvider extends StateNotifier<HelloState> {
HelloStateProvider() : super(HelloState());
void setHello(String hello) {
state = HelloState(
hello: hello,
);
}
}
final helloStateProvider = StateNotifierProvider<HelloStateProvider, HelloState>(
(ref) => HelloStateProvider(),
);
4、运行结果
四、使用代码生成
代码生成是指使用工具为我们生成代码。
在Dart中,它的缺点是需要额外的步骤来“编译”应用。 尽管这个问题可能会在不久的将来得到解决, 但Dart团队正在研究并解决这个问题的潜在方案。
使用Riverpod时,代码生成是完全可选的。 当然你也完全可以不使用。
与此同时,Riverpod支持代码生成,且推荐你使用它。
1、改造 hello_state.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'hello_state.g.dart';
class HelloList extends _$HelloList {
List<String> build() {
return ["hello world!"];
}
void addHello(String hello) {
state = [...state, hello];
}
}
2、代码生成
# --delete-conflicting-outputs 可选,会在生成代码冲突的时候,删除原来的代码,重新生成
flutter pub run build_runner build --delete-conflicting-outputs
3、改造 home_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../states/hello_state.dart';
class HomePage extends HookConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final List<String> hellos = ref.watch(helloListProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: SizedBox(
height: 400,
child: Column(
children: [
// 文本:遍历 hellos 列表
...hellos.map((e) => Text(e, style: const TextStyle(fontSize: 40),)),
// 更新文本
ElevatedButton(
style: ButtonStyle(minimumSize: MaterialStateProperty.all(const Size(200, 50))),
onPressed: () {
// 获取当前时间
DateTime now = DateTime.now();
ref.read(helloListProvider.notifier).addHello("hello ${now.second}");
},
child: const Text('Update'),
),
],
),
)),
);
}
}
4、运行结果
5、为什么在Riverpod中使用代码生成
你可能在想:“如果在Riverpod中代码生成是可选的,为什么要使用?”
让你的代码生活更简单。
这包括但不限于:
- 更好的语法, 更可读且更灵活,而且还能减少学习曲线。
- 不需要担心
FutureProvider
、Provider
还是其他 provider。仅需写下你的逻辑, Riverpod将为你选择最合适的provider。 - 向provider传递参数现在不受限制。不再局限于使用 family 和传递单个参数, 现在可以传递任何形式的参数。这包括命名参数、可选参数甚至默认值。
- 不需要担心
- 在Riverpod中编写的代码支持 有状态热重载。
- 更好地调试,通过生成额外的元数据然后用调试器调试。
- Riverpod的一些功能将只支持代码生成。
与此同时,许多应用程序中已经使用了代码生成比如 Freezed 或 json_serializable。 在这种情况下,你的项目可能已经为代码生成配置好了,使用Riverpod应该很简单。