在前端系统中,“状态管理”非常重要。
之前介绍的“StatefulWidget”也是执行状态管理的功能之一,
但是,在实现具有多个屏幕和功能的应用程序时,需要管理的状态也会增加,这使得管理变得更加困难。
Riverpod是一个能够简化状态管理的优秀包。本文将介绍Riverpod。
本文介绍的Riverpod是v2版本。
什么是Riverpod
Riverpod是一个用于定义全局状态(State)的工具。
对于变量或常量,通过改变定义的范围,可以相对容易地定义全局变量。
但是,在前端系统中使用的状态(State)与Widget紧密相关,
状态的变化需要重新构建Widget,因此不是那么简单。
而且,当多个Widget需要引用相同的状态信息时,情况会变得更加复杂。
Riverpod就是一个可以简化这一过程的工具。
尽管Riverpod不仅仅是用于全局变量的工具,但本文将仅进行基本介绍。
关于状态管理
在介绍Riverpod之前,先来了解一下状态管理。
状态(State)
简单来说,状态(State)是影响屏幕上信息的变量。
例如,登录用户信息、待办事项列表信息、
复选框的选中状态等,这些影响屏幕显示的数据都是状态(State)。
Widget会监视这些状态,并在每次变化时重新构建屏幕。
状态管理
状态管理是指适当更新或删除这些状态,并在屏幕上表示这些状态的过程。
状态分为本地状态和全局状态。
- 本地状态
仅在一个屏幕或一个Widget中使用的状态。
例如,仅在该屏幕中使用的文本字段或计数器的值等。 - 全局状态
在应用程序中共享的状态。
例如,在首页或用户信息页面显示的用户信息等。
Flutter提供了“StatefulWidget”来进行状态管理。
但是,尝试使用“StatefulWidget”管理全局状态会变得复杂。
如果需要在其他屏幕传递状态或在其他屏幕更新状态,
则需要实现某些功能,如在使用状态之前进行同步。
使用Riverpod,Provider负责监视状态,并自动将影响传递给相关Widget,
因此不需要实现上述同步功能。
关于Riverpod
Riverpod是一个旨在简化Flutter中的状态管理的开源包。它通过以下功能进行状态管理:
- Notifier
通知者(Notifier)负责管理目标状态和更新删除的逻辑。
它负责更新状态和将状态变更通知给Widget。 - Provider
Widget使用提供者(Provider)来引用或更新通知者(Notifier)的值。
使用Riverpod,像上述图像中一样,WidgetA的更新也会传递到WidgetB!
使用Riverpod(概要)
使用Riverpod时,需要准备以下内容:
- 状态类
- 通知者(Notifier)
- 提供者(Provider)
关于“3. 提供者(Provider)”,如果是v2.0及以后版本,可以通过“Riverpod Generator”自动生成。
Riverpod的基本使用方法
以待办事项应用为例,介绍基本使用方法。
配置文件
在pubspec.yaml中添加以下包。
dependencies:
flutter_riverpod:
riverpod_annotation:
freezed_annotation:
dev_dependencies:
build_runner:
riverpod_generator:
Riverpod相关的包
flutter_riverpod:用于在Flutter中使用Riverpod的包
riverpod_generator:自动生成Riverpod的Provider的包
riverpod_annotation:用于判断riverpod_generator中使用的注释的包
其他包
build_runner:实际进行代码自动生成的包
freezed:创建不变类的包
freezed_annotation:用于判断freezed中使用的注释的包
状态类的定义
作为状态使用的类需要是不变(Immutable)类。
可以使用freezed等工具定义不变类,如下所示:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'todo.freezed.dart';
@freezed // freezed注释
class Todo with _$Todo {
factory Todo({
required String id,
required String description,
required bool completed,
}) = _Todo;
}
不变的好处是什么?
不变类是指无法更新值的类。
也就是说,一旦创建就无法更改其值。听起来有些不便。
非不变类的实例的数据传递是“引用传递”。
将实例传递给其他类或函数时,传递的不是值本身,而是包含值的地址值。
如果更新了作为参数传递给类或函数的实例的值,调用方的实例的值也会被更新。
这本身似乎适用于状态管理,但引用传递的麻烦之处在于,
即使是作为只读传递的实例,也可以更新地址值的值,可能会导致意外的更新。
此外,这种更新不会产生错误,因此无法检测。
例如,如果复制实例并对其进行排序,复制源也会被排序,这样的意外更新就会发生。
Riverpod希望通过使用不变类管理状态来阻止这种意外变更,只在有意更改时才更新状态。
Notifier的定义
根据上述定义的类定义Notifier。
在这里,描述更新状态和添加新状态的处理。
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'todo.dart';
// 按照以下步骤执行build runner,将创建此文件
part 'todo_notifier.g.dart';
@riverpod
class TodoNotifier extends _$TodoNotifier {
@override
List<Todo> build() {
return [];
}
// 添加新项
void addTodo(Todo todo) {
state = [...state, todo];
}
// 删除
void removeTodo(String todoId) {
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}
// 切换“completed”的状态
void toggle(String todoId) {
state = [
for (final todo in state)
if (todo.id == todoId)
todo.copyWith(completed: !todo.completed)
else
todo,
];
}
}
本文仅介绍了Riverpod的概要,因此省略了详细的处理说明。
执行Riverpod Generator
在终端中执行以下命令运行build_runner:
flutter pub run build_runner build --delete-conflicting-outputs
然后,Riverpod Generator将启动,并创建〜.d.dart文件。
由于上述命令也会启动freezed,因此也会创建〜.freezed.dart文件。
Riverpod Generator将创建Provider。
查看创建的〜.d.dart文件,您将看到定义了Provider。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todo_notifier.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$todoNotifierHash() => r'1330ee68f148d9ff18b1e0370e3f3541ab67c621';
@ProviderFor(TodoNotifier)
final todoNotifierProvider =
AutoDisposeNotifierProvider<TodoNotifier, List<Todo>>.internal(
TodoNotifier.new,
name: r'todoNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$todoNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TodoNotifier = AutoDisposeNotifier<List<Todo>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
使用Riverpod的Widget
使用Riverpod时,需要声明使用范围。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'todo_item.dart';
import 'todo_notifier.dart';
void main() {
// Riverpodを使用するスコープを"ProviderScope"で宣言
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoItemListPage(),
);
}
}
// 使用“ConsumerWidget”定义使用Riverpod的Widget
class TodoItemListPage extends ConsumerWidget {
@override
// “WidgetRef ref”是使用Provider的上下文
Widget build(BuildContext context, WidgetRef ref) {
// “ref.watch”监视Provider,并检测状态变更。
List<TodoItem> todoItems = ref.watch(todoNotifierProvider);
return Scaffold(
appBar: AppBar(title: Text('todoItem List')),
body: ListView.builder(
itemCount: todoItems.length,
itemBuilder: (context, index) {
final todoItem = todoItems[index];
return ListTile(
title: Text(todoItem.title),
leading: Checkbox(
value: todoItem.completed,
onChanged: (bool? newValue) {
// “ref.read”从Provider中调用Notifier的方法。
ref.read(todoNotifierProvider.notifier).toggle(todoItem.id);
},
),
onLongPress: () => ref.read(todoNotifierProvider.notifier).removeTodo(todoItem.id),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// addTodo
},
child: Icon(Icons.add),
),
);
}
}
“ref”是reference(引用)的缩写。
Widget到状态的引用路径是ref→Provider→Notifier→State。
※上述处理中未使用Notifier的“addTodo”。
执行屏幕
执行后,如下所示,将显示初始数据。
点击复选框或长按列表项,状态将发生变化。
完成!!
最后
本文介绍了Riverpod的基本内容。Riverpod具有多种功能,希望未来能介绍这些功能。
希望本文能帮助您理解Riverpod。 感谢您的阅读!!