Flutter 简介 Provider

标签: 编程学习 Flutter学习

什么是Provider?

Provider是 Flutter 官方推荐的状态管理方式之一,它的特点是:不复杂,好理解,代码量不大的情况下,可以方便组合和控制刷新颗粒度。 Provider是 是可继承组件 InheritedWidget 的一个外包,但它允许向外暴露任何状态对象,包括了 bloC、stream、future 等等。

Provider 分类

  1. 基础 Provider
  2. ChangeNotifierProvider
  3. StreamProvider
  4. FutureProvider
  5. ValueListenableProvider
  6. MultiProvider
  7. ProxyProvider

基本使用

基础 Provider

Provider 无法知道我们更新过了 Model 的属性,也无法告知 Widget 响应改变从而作出更新

class UserModel {
  int counter = 0;

  Future<void> increment() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    print(counter);
  }

  dispose() {}
}

class DemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<UserModel>(
      create: (_) => UserModel(),
      dispose: (_, v) => v.dispose(),
      child: Scaffold(
          appBar: AppBar(
            title: Text('测试'),
          ),
          body: Consumer<UserModel>(builder: (_, value, child) => Center(
            child: FlatButton(
              color: Colors.lightBlue,
              child: Text('${value.counter}'),
              onPressed: () {
                value.increment();
              },
            ),
          ),
        ),
      ),
    );
  }
}

ChangeNotifierProvider

要实现上面的 Model 改变且展现在 Widget 上,使用这个 Provider 就可以,只需要稍加改造:

class UserModel extends ChangeNotifier {
  int counter = 0;

  increment() {
    counter++;
    print(counter);

    notifyListeners();
  }
}

class DemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<UserModel>(
      create: (_) => UserModel(),
      child: Scaffold(
          appBar: AppBar(
            title: Text('测试'),
          ),
          body: Column(
            children: [
              Consumer<UserModel>(builder: (_, value, child) => Text('${value.counter}'),),

              Builder(builder: (context){
                return FlatButton(
                  color: Colors.lightBlue,
                  child: Text('点我'),
                  onPressed: () {
                    final model = Provider.of<UserModel>(context);
                    model.increment();
                  },
                );
              }),
            ],
          ),
        ),
    );
  }
}

看起来与前面我们自己做的 Provider 很类似,这里当然也会遇到我们前面遇到的问题,为了避免这样的事情发生,我们可以使用如下的语法让模型不再注册重新构建的监听:

final model = Provider.of<MyModel>(context, listen: false);

StreamProvider

StreamProvider 给人的第一印象是:好像并不那么有必要。毕竟在 Flutter 中,我们可以使用常规的 StreamBuilder 来订阅流信息:

class DemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: currentStream,
      builder: (_, AsyncSnapshot<UserModel> snapshot) {
        return Text('${snapshot.data.counter}');
      },
    );
  }
}

而如果想使用 Provider 来完成,我们可以在根结点通过 StreamProvider 暴露出这个流:

StreamProvider<UserModel>.value(
  stream: currentStream,
  child: DemoWidget(...),
}

然后在子组件中就可以像其他 Provider 那样使用了:

@override
Widget build(BuildContext context) {
  return Consumer<UserModel>(
    builder: (context, value, child) => Text(value.counter),
  );
}

除了能让组件代码更加清晰,它也可以抽象并过滤掉数据是否是来自于流的这一信息。

FutureProvider

和上面的例子类似,FutureProvider 是在组件中使用 FutureBuilder 的替换方案。这里是一段代码示例:

FutureProvider<UserModel>.value(
  value: currentUser,
  child: DemoWidget(...),
);

我们可以使用 Consumer 来在子元素中获取到这个值。

ValueListenableProvider

ValueListenable 是 ValueNotifier 类实现的 Dart 接口,它可以在自身接收的参数发生变化的时候通知监听者

class CustomModel {
  final ValueNotifier<int> counter = ValueNotifier(0);  
}

如果我们使用的是复杂类型的参数,ValueNotifier 将会使用 == 操作符来确认是否参数值变化了。 创建一个基础 Provider 用来容纳主模块,它同时还有一个 Consumer,以及一个用于监听 counter 属性的嵌套的 ValueListenableProvider:

Provider<CustomModel>(
  builder: (context) => MyModel(),
  child: Consumer<CustomModel>(builder: (context, value, child) {
    return ValueListenableProvider<int>.value(
      value: value.counter,
      child: DemoWidget(...)
    }
  }
}

注意:嵌套的 provider 的类型是 int。当然你的代码也会有其他可能的类型。如果有多个 Provider 都注册为同一类型,那么 Provider 将会返回最“近”的一个(距离最近的父级组件)。

如下代码可以监听任意子组件的 counter 属性:

class DemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<int>(
      builder: (context, value, child) {
        return Text(value.toString());
      },
    );
  }
}

如下代码可以更新其他组件的 counter 属性。注意:我们首先需要获取原始的 CustomModel 实例。

class OtherWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FlatButton(
      child: Text('点我'),
      onPressed: () {
        final model = Provider.of<CustomModel>(context);
        model.counter.value++;
      },
    );
  }
}
  1. MultiProvider 如果我们应用了多个 Provider 组件,可以使用 MultiProvider,它允许我们在同一层级声明所有的 provider。但这仅仅是一种语法糖,它们实际上还是嵌套的,但是用起来一目了然,所以我们用的会比较多

    MultiProvider( 
    providers: [ 
    Provider<ModelA>.value(value: '1'), 
    Provider<ModelB>.value(value: '2'), 
    Provider<ModelC>.value(value: '3'), 
    ], 
    child: MaterialApp(...), 
    )
  2. ProxyProvider 当我们需要建立有赖于其他服务的根服务集时,就很需要 ProxyProvider 了,第一个泛型参数是 ProxyProvider 的类型,第二个是它需要返回的类型。
    MultiProvider ( 
    providers: [ 
    Provider<ModelA> ( 
      builder: (context) => ModelA(),
    ), 
    ProxyProvider<ModelA, ModelB>(
      builder: (context, value, previous) => ModelB(value),
    ), 
    ], 
    child: App(...),
    )

同时监听多个 Provider

如果我们想要一个组件同时监听多个 Provider,并且当任意一个被监听的 Provider 发生变化时都要重构组件,那就可以使用 Consumer 组件,最多可以监听 6 个 Provider

Consumer2<UserModel, int>(
  builder: (context, value, value2, child) {
    //value 是 MyModel 类型
    //value2 是 int 类型
  },
);