这篇MD我默认你已经知道了Provider和Riverpod,以及已经安装了Riverpod,现在只是想知道如何使用Riverpod,请往下看。(如果不知道怎么安装,或者不知道什么是状态管理,可以先补习一下状态管理方面的知识)
1. 如何获取Ref
可以使用以下三种方式获取 Ref
1. 使用 ConsumerWidget获取
例如
scala
final helloWorldProvider = Provider<String>((_) => 'Hello world');
// 1. 继承 ConsumerWidget
class HelloWorldWidget extends ConsumerWidget {
@override
// 2. Method中增加了 WidgetRef参数
Widget build(BuildContext context, WidgetRef ref) {
// 3. 使用 ref.watch() 获取helloWorldProvider的状态
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
}
}
将 Widget
继承自 ConsumerWidget
而不是 StatelessWidget
,这种方式是最常见的也是最简单的。
2. 使用 Consumer
将需要使用 ref 的 widget 用 Consumer 包裹,从而拿到 ref
scala
final helloWorldProvider = Provider<String>((_) => 'Hello world');
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 1. 使用 Consumer
return Consumer(
// 2. 通过 builder 拿到 ref
builder: (_, WidgetRef ref, __) {
// 3. 使用 ref.watch() 获取helloWorldProvider的状态
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
},
);
}
}
那我们什么时候使用
Consumer
,什么时候使用ConsumerWidget
?上面这个例子中, 只有
Text
这个widget
需要更新,监听状态,其他的Widget
都不需要更新,那么我们可以使用Consumer
来包裹,从而提升性能。(Tip : 在复杂的页面中,这非常有用,无法抽取出去的Widget
,可以使用Consumer
包括,减少刷新粒度。)
3. 使用 ConsumerStatefulWidget & ConsumerState
相应的, ConsumerWidget
对应 StatelessWidget
, 那么 ConsumerStatefulWidget & ConsumerState
对应 StatefulWidget
2. 八种Provider的用法
八种Provider分别是:
- Provider
- StateProvider
- StateNotifierProvider
- FutureProvider
- StreamProvider
- ChangeNotifierProvider
- NotifierProvider (2.0 新的API)
- AsyncNotifierProvider (2.0 新的API)
1. Provider
Provider 非常适合访问不改变的依赖项和对象。
可以使用它来访问存储库、Logger 或者其他不包含可变状态的类。
例如,你可以返回一个DateFormat
scala
// 一个无需更新的Provider
final dateFormatterProvider = Provider<DateFormat>((ref) {
return DateFormat.MMMEd();
});
class SomeWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 获取 DateFormat
final formatter = ref.watch(dateFormatterProvider);
// 使用
return Text(formatter.format(DateTime.now()));
}
}
2. StateProvider
StateProvider
非常适合存储可以更改的简单状态对象 ,比如 counter
value
ini
final counterStateProvider = StateProvider<int>((ref) {
return 0;
});
如果你在 build 方法中watch 这个 provider,那么当状态改变时,这个widget将会进行 rebuild 刷新。
你也可以在 button的点击事件中通过 ref.read() 来更新状态。
scala
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. 监听counterStateProvider并且当状态改变时会rebuild
final counter = ref.watch(counterStateProvider);
return ElevatedButton(
// 2. 使用状态
child: Text('Value: $counter'),
// 3. 在button的点击事件中,改变状态
onPressed: () => ref.read(counterStateProvider.notifier).state++,
);
}
}
StateProvider
是非常适合存储简单状态变量,比如enums、strings、booleans,numbers
。Notifier 也可以用于相同的目的并且更灵活。对于更复杂或异步状态,请使用 AsyncNotifierProvider、FutureProvider 或 StreamProvider,如下所述。
3. StateNotifierProvider
使用这个 StateNotifierProvider来监听和暴露 StateNotifier
StateNotifierProvider 和 StateNotifier 是管理可能因事件或用户交互而改变的状态的理想选择。
如下示例:
scala
import 'dart:async';
class Clock extends StateNotifier<DateTime> {
// 1. 初始化
Clock() : super(DateTime.now()) {
// 2.创建一个每一秒都更新一次的状态机
_timer = Timer.periodic(Duration(seconds: 1), (_) {
// 3. 更新状态
state = DateTime.now();
});
}
late final Timer _timer;
// 4. 销毁
@override
void dispose() {
_timer.cancel();
super.dispose();
}
}
// 创建一个StateNotifierProvider,有两个 注解
final clockProvider = StateNotifierProvider<Clock, DateTime>((ref) {
return Clock();
});
class ClockWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 监听 StateNotifierProvider
final currentTime = ref.watch(clockProvider);
// 改变输出
final timeFormatted = DateFormat.Hms().format(currentTime);
return Text(timeFormatted);
}
}
以上示例中可以看到,使用 ref.watch(clockProvider)
,widget 将会在每次状态更新时进行重构,更新 Text 显示。
笔记:
ref.watch(clockProvider)
返回的是 Provider的状态。如果需要获取 通知对象(Notifier),可以使用ref.watch(clockProvider.notifier)
,这个示例返回的是Clock
想要获取如何以及何时使用 StateNotifierProvider更加完整的示例,可以查看这篇文章
如果您只需要读取一些异步数据,那么使用 StateNotifierProvider
就太过分了。这就是 FutureProvider
的用途
4. FutureProvider
想要从返回 Future 的 API 调用中获取结果?
那么可以像下面这样创建一个 FutureProvider
rust
final weatherFutureProvider = FutureProvider.autoDispose<Weather>((ref) {
// 获取下面 weatherRepositoryProvider 的结果
final weatherRepository = ref.watch(weatherRepositoryProvider);
// 调用函数获取 Future<Weather>
return weatherRepository.getWeather(city: 'London');
});
// weather repository provider
final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
return WeatherRepository();
});
FutureProvider
通常和autoDispose
修饰符一起使用
然后你可以在 build 方法中观察它并使用模式匹配将结果 AsyncValue (数据,加载,错误)映射到你的 UI
javascript
Widget build(BuildContext context, WidgetRef ref) {
// 监听 FutureProvider 得到一个 AsyncValue<Weather>
final weatherAsync = ref.watch(weatherFutureProvider);
// 更新 UI
return weatherAsync.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (weather) => Text(weather.toString()),
);
}
注意:当监听
FutureProvider
或StreamProvider
时,返回类型是AsyncValue
。AsyncValue
是 Riverpod 中处理异步数据的实用类。有关详细信息,请阅读:Flutter Riverpod 提示:使用 AsyncValue 而不是 FutureBuilder 或 StreamBuilder
FutureProvider
非常强大,你可以使用它做以下操作:
-
执行和缓存异步操作(例如网络请求)
-
处理异步操作的错误和 loading 状态
-
将多个异步值组合成另一个值
-
重新获取和刷新数据(搭配 pull-to-refresh 进行使用)
5. StreamProvider
使用 StreamProvider 监听来自实时 API 的结果流并响应式地重建 UI。
以下示例
csharp
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
// 获取 FirebaseAuth
final firebaseAuth = ref.watch(firebaseAuthProvider);
// 调用方法,返回一个 Stream<User?>
return firebaseAuth.authStateChanges();
});
// 提供一个 FirebaseAuth 实例
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) {
return FirebaseAuth.instance;
});
/// 使用
Widget build(BuildContext context, WidgetRef ref) {
//监听 StreamProvider 得到 AsyncValue<User?>
final authStateAsync = ref.watch(authStateChangesProvider);
// 更新 UI
return authStateAsync.when(
data: (user) => user != null ? HomePage() : SignInPage(),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
StreamProvider 搭配 StreamBuilder widget 将有许多的便捷之处。
6. ChangeNotifierProvider
通过使用 ChangeNotifier,我们可以存储一些状态并且能够监听他们的改变。
如下示例中,我们可以使用 ChangeNotifierProvider 来包裹 ChangeNotifier
csharp
class AuthController extends ChangeNotifier {
User? user;
bool get isSignedIn => user != null;
Future<void> signOut() {
user = null;
notifyListeners();
}
}
final authControllerProvider = ChangeNotifierProvider<AuthController>((ref) {
return AuthController();
});
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () => ref.read(authControllerProvider).signOut(),
child: const Text('Logout'),
);
}
使用 ChangeNotifier API 可以轻松打破两个重要规则:不可变状态和单向数据流。
因此,不鼓励使用 ChangeNotifier,我们应该改用 StateNotifier。
7. NotifierProvider 和 AsyncNotifierProvider
这两个Provider出现在 Riverpod 2.0之后, Notifier
和 AsyncNotifier
旨在取代 StateNotifier
并带来一些新的好处:
- 更容易执行复杂的异步初始化
- 更人性化的 API:不再需要传递 ref
- 如果我们使用 Riverpod Generator,那就不再需要手动声明提供者
通过以下示例来了解这些好处。
计数器示例:
在介绍StateProvider使用中,举了一个计数器示例。如你看到的,确实比较简单方便,但是如果状态稍微复杂一些,那么StateProvider可能不再适用,可能需要 StateNotifierProvider,那么现在我们可以直接使用 Notifier。
-
创建 Notifier 类
scalaimport 'package:flutter_riverpod/flutter_riverpod.dart'; class Counter extends Notifier<int> { @override int build() { return 0; } void increment() { state++; } }
这里做了两件事, build 方法中初始化, 暴露一个可更改状态的方法。
-
创建一个provider来包装这个类
arduinofinal counterProvider = NotifierProvider<Counter, int>(() { return Counter(); }); //或者 final counterProvider = NotifierProvider<Counter, int>(Counter.new);
-
现在我们可以使用这个Provider
scalaimport 'counter.dart'; class CounterWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // 1. 监听provider final counter = ref.watch(counterProvider); return ElevatedButton( // 2. 使用 child: Text('Value: $counter'), // 3. 更新状态 onPressed: () => ref.read(counterProvider.notifier).state++, ); } }
我们可以使用
ref.read(counterProvider.notifier).increment()
-
现在来比较
StateProvider
和NotifierProvider
到目前为止,我们已经了解到当我们需要修改简单变量时,StateProvider 可以很好的实现。 但是如果我们的状态(以及更新它的逻辑)更复杂,Notifier 和 NotifierProvider 是一个很好的选择,并且也很容易实现。
Notifier 可以使用 riverpod_generator 自动生成 Provider
scalaimport 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter.g.dart'; @riverpod class Counter extends _$Counter { @override int build() { return 0; } void increment() { state++; } }
其中 _$Counter 最终被解析成为了
scala/// See also [Counter]. final counterProvider = AutoDisposeNotifierProvider<Counter, int>( Counter.new, name: r'counterProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : $CounterHash, ); typedef CounterRef = AutoDisposeNotifierProviderRef<int>; abstract class _$Counter extends AutoDisposeNotifier<int> { @override int build(); }
可以看到
_$Counter
继承自AutoDisposeNotifier
, 且 重载了 build,而AutoDisposeNotifier
接受的状态,是根据 build 返回的,所以 Counter 需要 override build 方法。csharp/// {@template riverpod.notifier} abstract class AutoDisposeNotifier<State> extends BuildlessAutoDisposeNotifier<State> { /// {@macro riverpod.asyncnotifier.build} @visibleForOverriding State build(); }
这就是为什么 Provider 可以检测到 State