Riverpod3.0 基本概念之 Provider

42 阅读7分钟

Providers

Providers是Riverpod的核心功能。使用Riverpod就是使用Riverpod的各种Provider。

什么是Provider

Provider本质上就是“备忘录方程”,只不过对其进行了封装,以提高其易用性。 也就是说,使用相同的参数调用Provider时,会返回已缓存的值(而不是重复请求)。 最常见的应用示例是使用Provider执行网络请求。 假设有这样一个方程,通过一个API调用该方程请求一条user数据:

Future<User> fetchUser() async {
    final response = await http.get('https://api.example.com/user/123');
    return User.fromJson(response.body);
}

这个方程需要处理一个问题,就是如果在widget内调用该方程,那我们就需要自己处理返回结果的缓存问题;并找到一个能将该结果分享出去的方法,以便所有需要该结果的widget都能够访问到。 这就是Provider的优势所在。Provider对方程进行了包装。Provider缓存了上述方程的请求结果,并允许多个widget访问同一个值:

// 与fetchUser方程等价,但缓存了请求结果。
// 多次调用userProvider返回相同的值
final userProvider = FutureProvider<User>((ref) async {
    final response = await http.get('https://api.example.com/user/123');
    return User.fromJson(response.body);
});

除了基本的缓存功能,Provider还添加了多种功能:

  • 内置的缓存失效机制 需要强调的是,使用Ref.watch就可以将多个缓存拼接起来,并在必要时对缓存做失效处理。
  • 自动清理功能(Automatic Disposal) 不再使用时,自动释放Provider占用的资源。
  • 数据绑定(Data-binding) Provider可以完全替换掉FutureBuilderStreamBuilder
  • 自动处理出错 Provider可以自动捕获出错,并将出错暴露给UI。
  • 支持数据仿造(Mocking support) 为方便测试或其他目的,所有的Provider都可以被仿造(mocked)。详见Provider overring[^1^](#annotation1)。
  • 离线持久化(实验功能)
  • 形式转换实验功能)

Provider有六种形式

SynchronousFutureStream
UnmodifiableProviderFutureProviderStreamProvider
ModifiableNotifierProviderAsyncNotifierProviderStreamNotifierProvider

对表格中的内容的解释如下。 Sync vs Future vs Stream

表格中的各列是Dart自带的Function类型。

int synchronous() => 0;
Future<int> future() async => 0;
Stream<int> stream() => Stream.value(0);

Unmodifiable vs Modifiable

Widget默认不可以修改Provider,而“Notifier”形式的Provider则可以被外部修改。 这类似于私有化的setter(不可修改的Provider):

// _state变量允许内部修改,不允许外部修改
var _state = 0;
int get state => _state;

对应于共有setter(可修改的Provider):

// 任何对象都可以修改state变量
var state = 0;

附记 原则上,也可以将 不可修改 vs 可修改 分别对应与 StatelessWidget vs StatefulWidget。 这种对应方式并不非常准确,因为Provider并不是Widget,而且两者都可以保存状态。但原理是一致的:“一个对象,不可变”对应“两个对象,可变”。

创建一个Provider

Provider必须被创建为顶级声明。 也就是说,provider不可以声明在类中,也不可以声明在方程中。 创建语法取决于Provider是“可修改的”还是“不可修改的”。

不可修改方式
final name = SomeProvider.someModifier<Result>((ref) {
    // 此处书写逻辑代码
});

provider 该变量稍后用于与其他provider交互。 variable 该变量必须被声明为final类型和顶级类型(也就是全局类型)。


provider 通常是Provider或FutureProvider或StreamProvider type Provider类型取决于方程的返回值类型。例如,要创建一个Future,就需要调用FutureProvider。

最常用的是 FutureProvider


Modifier 通常在Provider类型之后会有一个“modifier”。 Modifier是可选的,通常以类型安全的方式用于对Provider的行为进行微调。 当前有两种类型的modifier:

autoDispose 在Provider被使用完毕之后自动清理缓存。 family 用以向Provider传递参数


Ref 该对象用于与其他Provider交互。 所有Provider都有一个Ref对象,该对象或者以变量形式存在与Provider方程中,或者作为一个属性存在与Notifier中。


provider function Provider的逻辑代码就放置在这里。首次调用Provider时晖调用该方程。 之后的读取操作就不会再访问该方程,而是返回缓存值

可修改方式
final name = SomeNotifierProvider.someModifier<MyNotifier, Result>(MyNotifier.new);

class MyNotifier extends SomeNotifier<Result> {
    @override
    Result build() {
        // 此处书写代码逻辑
    }
    // 此处书写自定义方法
}

provider 该变量稍后用于与其他provider交互。 variable 该变量必须被声明为final类型和顶级类型(也就是全局类型)。


provider 通常是NotifierProvider或AsyncNotifierProvider或StreamNotifierProvider。 type Provider类型取决于方程的返回值类型。例如,要创建一个Future,就需要调用AsyncNotifierProvider。 最常用的类型是AsyncNotifierProvider。


Modifier 通常在Provider类型之后会有一个“modifier”。 Modifier是可选的,通常以类型安全的方式用于对Provider的行为进行微调。 当前有两种类型的modifier:

autoDispose 在Provider被使用完毕之后自动清理缓存。 family 用以向Provider传递参数


Notifier constructor 参数“notifier provider”是一个用来初始化“notifier”对象的方程。 该对象通常应该“constructor tear-off”(实在不知该如何准确翻译,此处保留原貌).


Notifier 如果将NotifierProvider等同于StatefulWidget,那Notifier就等同于'State'类。 该类负责将修改Provider的状态的方式暴露出去。Consumer对象可以通过ref.read(yourProvider.notifier).yourMethod()的方式访问该类里面的共有方法。

提示 不要将代码逻辑书写在notifier的构造器中。 因为此时ref以及其他属性还不可用。而应把代码逻辑书写在build方法中。


Notifier type 自定义notifier继承的基类应与provider及“family”中的类型匹配,例如

  • NotifierProvider -> Notifier
  • AsyncNotifier -> AsyncNotifier
  • StreamNotifierProvider -> StreamNotifier

build 所有自定义notifier都必须重写build方法。 这个方法就相当于在非notifier类型的Provider中书写代码逻辑的地方。 不要直接调用该方法。

附记 可以声明任意多个Provider。Riverpod允许对同一种类型的状态创建多个Provider。

final cityProvider = Provider((ref) => 'London');
final countryProvider = Provider((ref) => 'England');

这两个Provider都是String类型。

使用Provider

Provider自身什么也不做,在这一点上与Widget类似。 就如同Widget是对UI的描述,Provider是对状态的描述。 只不过,Provider是完全无状态的,而且还可以被初始化为const——只不过这样一来在语法上就会变得有点罗嗦。 使用Provider需要一个独立对象:ProviderContainer。 在使用Provider之前,先将Flutter应用放入一个ProviderScope中:

void main() {
    runApp(
        ProviderScope(child:MyApp())
    );
}

完成这一步之后,就需要获取一个Ref对象用于与Provider交互。 获取Ref有两种方式:

  • Provider自身有一个Ref对象 该对象是Provider方程的第一个参数,或Notifier的属性。如此Provider就可以与其他Provider相互沟通。
  • Widget tree需要名为Consumers的特殊类型Widget。Consumers通过提供一个WidgetRef对象,为Widget tree与Provider tree之间架起了沟通的桥梁。

假设有一个helloWorldProvider返回一个字符串。调用示例如下:

class Example extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Consumer(
            builder:(context, ref, _) {
                // 获取Provider的值
                final helloWorld = ref.watch(helloWorldProvider);
                // 在UI使用获取到的值
                return Text(helloWorld);
            }
        );
    }
}

1 Provider overring 在Riverpod中,所有的Provider都可以被重写,以改变其行为。该功能对测试、调试,或这为不同的场景提供不同的Provider实现——甚至 Scoping provider——都很方便。 在ProviderContainer/ProviderScope中通过override参数完成对Provider的重写。在该参数中可以指定一系列指令,以完成对特定Provider的重写。 通过自定义Provider结合overrideWithSomething,这些重写指令就可以被创建出来。 有一系列这类方法,所有的这些方法都是以overrideWith开头的,包括:

  • overrideWith
  • overrideWithValue
  • overrideWithBuild 一个典型的重写示例如下:
void main() {
    runApp(
        ProviderScope(
            overrides: [
                // 所有的重写操作都写在这里
                // 下面的代码演示如何重写一个“counter provider”以便使用不同的初始值
                counterProvider.overrideWith((ref) => 42),
            ]
        )
    );
}