Ref是与Provider交互的基本方式。
对Provider而言,Ref类似于Flutter中的BuildContext。使用ref可以完成的部分工作如下:
-
对一个Provider执行读取与观测操作。
-
检查Provider是否已加载
-
重置Provider的状态
除此之外,Ref还可以让Provider观测自身状态的生命周期。就像Widget中的 initSate 与 dispose 方法,Provider中的相应方法包括 onDispose、 onCancel 等等。
获取一个Ref对象的方式
在应用的不同位置,就会有不同的获取Ref对象的方法。
Provider默认就可以获取一个Ref对象。它做为一个参数存在于初始化方程中,也作为一个属性存在与Notifier类中。
final myProvider = Provider<int>((ref) {
// 在这里就可以使用ref对象
});
final myNotifierProvider = NotifierProvider<MyNotifier, int>(MyNotifier.new);
class MyNotifier extends Notifier<int> {
@override
int build() {
// 在notifier的任何地方都可以使用 this.ref
ref.watch(someProvider);
}
}
要在Widget中获取Ref对象,就需要使用Consumer。
Consumer(
builder: (context, ref, _) {
// 此处可以访问ref对象
finl value = ref.watch(myProvider);
return Text('$value');
}
);
如果既不在Widget中,也不在Provider中,那该如何获取Ref对象?
即使不在Widget中也不在Provider中,只要能跟Widget或Provider建立一种哪怕非常弱的联系,就可以获取一个Ref对象。
在这种情况下,只需要先从Widget或者Provider中获取Ref对象,然后将这个对象传入相应的方程或对象中,如此即可。
void myFunction(WidgetRef ref) {
// 此处接受一个Ref对象
}
Consumer(
builder: (context, ref, _) {
return ElevatedButton(
onPressed: () => myFunction(ref); // 将Ref对象传入方程
child: Text('Click me'),
);
}
);
使用Ref与Provider交互
通常将与Provider的交互操作概括为如下两类:
-
监听Provider的状态
-
修改Provider的状态(比如重置或更新)
监听状态
Riverpod提供了两种监听方式:
-
Ref.watch声明式监听。最常用的监听方式,开发首选。 -
Ref.listen手动监听。标准的“addListen”式监听,功能更强大,但使用起来也更复杂。
如下示例,每秒更新一次Provider:
final tickProvider = NotifierProvider<Tick, int>(Tick.new);
class Tick extends Notifier<int> {
@overrride
int build() {
final timer = Timer.periodic(Duration(seconds: 1), (_) => state++);
ref.onDispose(timer.cancel);
return 0;
}
}
Ref.watch模式
该监听模式是Riverpod的特色功能。它可以将多个Provider无缝结合,并在Provider状态改变时便捷地更新UI。
使用Ref.watch的方式与在Flutter中使用InheritedWidget的方式相似。在Flutter中,当调用Theme.of(context)时,对应Widget就会订阅Theme,并在Theme发生改变时重建Widget。与之相似,当调用ref.watch(myProvider)时,对应的Widget或Provider就会订阅myProvider,并在myProvider发生改变时重建。
如下代码所示,当Provider实例Tick更新时,Consumer也会自动更新:
Consumer(
builder: (context, ref, _) {
final tick = ref.watch(tickProvider);
return Text('Tick: $tick');
}
);
Ref.watch最有意思的地方在于,Provider也可以使用它。
例如,我们可以创建一个Provider,让这个Provider返回“is tick divisible by 4?”。
final isDivisibleBy4Provider = Provider<bool>(
(ref) {
final tick = ref.watch(tickProvider);
return tick % 4 == 0;
}
);
然后,在UI界面监听这个新的Provider对象:
Consumer(
builder: (context, ref, _) {
final isDivisibleBy4 = ref.watch(isDivisibleBy4Provider);
return Text('Can tick be divided by 4? ${isDivisibleBy4}');
},
);
现在,UI就只会在Boolean值发生改变时才会更新,而不是每秒都更新。
Ref.listen模式
这种模式需要自己动手做一些配置,与ChangeNotifier的addListener方法相似,也与Stream.listen方法相似。
如果想要在provider的状态发生改变时执行其他操作,那么这个方法就很有用。这些“其他操作”包括显示一个对话框、跳转到其他页面、打印一条信息等等。
final exampleProvider = Provider<int>((ref) {
ref.listen(
tickProvider, (previous, next) {
// tickProvider发生改变时,如下方法就会被调用
print('Tick changed from $previous to $next');
}
);
return 0;
});
Consumer(
builder: (contex, ref, _) {
ref.listen(
tickProvider, (previous, next) {
// tickProvider发生改变时,如下方法就会被调用
print('Tick changed from $previous to $next');
}
);
return Text('Listening to tick changes');
}
);
重置状态
使用Ref.invalidate可以重置Provider的状态。
该方法会通知Riverpod清理当前状态,并在下一次读取Provider时重新计算。
如下代码会将tick重置为0:
Consumer(
builder: (context, ref, _) {
return ElevatedButton(
onPressed: () {
// 重置tickProvider,
// tick会重新从0开始计时
ref.invalidate(tickProvider);
},
child: Text('Reset tick'),
);
},
);
提示
如果需要在重置之后就获取一个新的状态,可以调用
Ref.read方法:
ref.invalidate(tickProvider);
final newTick = ref.read(tickProvider);
也可以使用
Ref.refresh方法,一步完成重置状态并读取新状态的操作。
final newTick = ref.refresh(tickProvider);
以上两段代码是等价的。
Ref.refresh是对Ref.invalidate与Ref.read的封装。
在用户交互操作中与Provider的状态进行交互
最后一个示例是在点击按钮时与Provider的状态实现交互。在这个场景中,不对状态进行监听操作。所以,会使用到Ref.read方法。
在点击按钮执行任务时,可以安全调用Ref.read方法。如下示例展示在点击按钮时打印当前tick值:
Consumer(
builder:(context, ref, _) {
return ElevatedButton(
onPressed: () {
// 读取当前tick值
final tick = ref.read(tickProvider);
print('Current tick: $tick');
},
child: Text('Print Tick'),
);
},
);
危险操作
不要试图使用
Ref.read替换Ref.watch优化代码,这样晖让代码变得不稳定,因为Provider行为的改变会引起UI与Provider的状态的不同步。
要选择
Ref.watch或slelect方法:
Consumer(
builder:(context, ref, _) {
// 不要这样操作。不要使用read作为忽视改变的手段
final tick = ref.read(tickProvider);
// 推荐操作。使用watch方法监听改变。
// 此处不会成为软件的瓶颈,不要在这里过度优化
final tick = ref.watch(tickProvider);
// 推荐操作。使用select方法,只监听state中指定数据
final isEven = ref.watch(tickProvider.select((tick) => tick.isEven),);
}
);
监听生命周期事件
Ref中有一个针对Provider的功能,就是监听生命周期事件。这些事件与Flutter控件中的initState、dispose以及其他生命周期方法类似。
生命周期监听方法使用“addListener”风格的API进行注册。监听方法是一些名字以on开头的方法,比如onDispose或onCancel。
final counterProvider = Provider<int> ((ref) {
ref.onDispose(
// 清理provider时会调用该方法
print('Counter provider is being disposed);
);
return 0;
});
提示
不需要手动注销这些监听方法。
Provider重置时,Riverpod会自动清理这些方法。
使用这些方法的返回值也可以手动注销这些方法。
final unregister = ref.onDispose(() {
print('This will never be called);
});
// onDispose方法在这里被注销
unregister();