精华内容
下载资源
问答
  • provider
    千次阅读
    2022-02-10 19:43:24

    一、为什么会有 Provider ?

    因为 Flutter 与 React 技术栈的相似性,所以在 Flutter 中涌现了诸如flutter_redux 、flutter_dva 、 flutter_mobx 、 fish_flutter等前端式的状态管理,它们大多比较复杂,而且需要对框架概念有一定理解。

    而作为 Flutter 官方推荐的状态管理 scoped_model ,又因为其设计较为简单,有些时候不适用于复杂的场景。

    所以在经历了一端坎坷之后,今年 Google I/O 大会之后, Provider 成了 Flutter 官方新推荐的状态管理方式之一。

    它的特点就是: 不复杂,好理解,代码量不大的情况下,可以方便组合和控制刷新颗粒度 , 而原 Google 官方仓库的状态管理 flutter-provide 已宣告GG , provider 成了它的替代品。

    二、Provider知识点

    1、 Provider 的内部 DelegateWidget 是一个 StatefulWidget ,所以可以更新且具有生命周期。

    2、状态共享是使用了 InheritedProvider 这个 InheritedWidget 实现的。

    3、巧妙利用 MultiProvider 和 Consumer 封装,实现了组合与刷新颗粒度控制。

    三、Provider的工作原理

    1、Delegate

    既然是状态管理,那么肯定有 StatefulWidget 和 setState 调用。

    在 Provider 中,一系列关于  StatefulWidget 的生命周期管理和更新,都是通过各种代理完成的,如下图所示,上面代码中我们用到的 ChangeNotifierProvider 大致经历了这样的流程:

    设置到 ChangeNotifierProvider 的 ChangeNotifer 会被执行 addListener 添加监听 listener。

    listener 内会调用 StateDelegate 的 StateSetter 方法,从而调用到  StatefulWidget 的 setState。

    当我们执行 ChangeNotifer 的 notifyListeners 时,就会最终触发 setState 更新。

    2、InheritedProvider

    状态共享肯定需要 InheritedWidget ,InheritedProvider 就是InheritedWidget 的子类,所有的 Provider 实现都在 build 方法中使用 InheritedProvider 进行嵌套,实现 value 的共享。

    注意:

    由于flutter sdk版本的不同,有些demo跑步起来,可能需要调整参数名称才行

    使用方式

    ChangeNotifierProvider 方式

    通过调用ChangeNotifier.notifyListenersChangeNotifier进行监听,将其公开给它的子Widget并重建依赖项;

    1. 绑定数据

    ChangeNotifierProvider绑定数据有两种方式:

    ChangeNotifierProvider({Key key, @required ValueBuilder<T> builder, Widget child })

    通过构造器创建一个ChangeNotifier,在ChangeNotifierProvider移除时自动处理;

    return ChangeNotifierProvider<User>(
            create: (BuildContext context) {
              print('ChangeNotifierProvider create');
              return User('赵云', 30);
            },
            builder: (context, child) {
              return HomePage();
            },
            child: MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(primarySwatch: Colors.blue,),
              home: HomePage(),
            )
        );

    ChangeNotifierProvider.value({Key key, @required T notifier, Widget child })

    通过监听通知给子Widget并重建依赖项;

    return ChangeNotifierProvider.value(
            value: User('赵云', 30),
            child: MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(primarySwatch: Colors.blue,),
              home: HomePage(),
            )
        );

    2.ListenableProvider 方式

    1. 数据绑定

    ListenableProvider({Key key, @required ValueBuilder<T> builder, Disposer<T> dispose, Widget child })

    通过构造器绑定数据并进行监听,当从Widget Tree中删除时dispose要销毁;注意:构造器builder不可为空;

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ListenableProvider<User>(
            builder: (_) => User('Flutter', 0),
            child: MaterialApp(
                title: 'Flutter Demo',
                theme: ThemeData(primarySwatch: Colors.blue),
                home: MyHomePage(title: 'Peovider Demo')));
      }
    }

    ListenableProvider.value({Key key, @required T listenable, Widget child })

    通过.value方式对数据进行监听listenable

    return ListenableProvider.value(
            value: User('赵云', 30),
            child: MaterialApp(
              title: 'Flutter Demo',
              theme: ThemeData(primarySwatch: Colors.blue,),
              home: HomePage(),
            )
        );

    2. 获取数据

       Provider 需要在数据绑定的子 Widget 中进行获取;使用静态方法 Provider.of<T>(BuildContext context),此方法从 BuildContext 关联的 Widget Tree 中查找最近的相同类型的数据进行展示;没有则报异常;

    Provider.of(context)方式

    @override
      Widget build(BuildContext context) {
        
        User user = Provider.of<User>(context);
        
        print('Provider :  ${user.name}');
    
        return Scaffold(
          appBar: AppBar(title: const Text("一叶飘舟教学"),),
          body: ListView(
            padding: const EdgeInsets.all(10),
            children: widget._homeDataList.map((HomeData homeData) {
              return HomeListItem(homeData: homeData);
            }).toList(),
          ),
        );
    }

    Consumer Widget构造器方式

    @override
      Widget build(BuildContext context) {
        
        return Consumer<User>(
            builder: (context, user, _) {
              return Text(user.name);
            }
        );
    }

    小结

    为方便理解,结合上一节的ChangeNotifierProvider,发现与ListenableProviderValueListenableProvider的使用基本相同;

    class ChangeNotifierProvider<T extends ChangeNotifier?>
        extends ListenableProvider<T> {}
    class ChangeNotifier implements Listenable {}
    class ValueListenableProvider<T> extends SingleChildStatelessWidget {}
    class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {}
    

    分析源码:ChangeNotifierProvider继承自ListenableProvider且对应的ChangeNotifier继承自listenable;算是ListenableProvider的子类;ValueNotifier继承自ChangeNotifier也与ChangeNotifierProvider相似;

    使用ChangeNotifierProviderValueListenableProvider绑定实体类时需要注意分别继承对应的ChangeNotifierValueNotifier

    classUserwithChangeNotifier{}classPersonextendsValueNotifier<User>{}

    无论使用那种.value方式,均建议在dispose中进行listener的关闭;

    @override

    void dispose(){

    stream.dispose();

    super.dispose();

    }

    Provider分类

    你也可以在 main 方法中通过下面这行代码来禁用此提示。 Provider.debugCheckInvalidValueType = null;

    这是由于 Provider 只能提供恒定的数据,不能通知依赖它的子部件刷新。提示也说的很清楚了,假如你想使用一个会发生 change 的 Provider,请使用下面的 Provider。

    ListenableProvider

    ChangeNotifierProvider

    ValueListenableProvider

    StreamProvider

    这几个 Provider 有什么异同。

    先关注 ListenableProvider / ChangeNotifierProvider 这两个类。

    ListenableProvider 提供(provide)的对象是继承了 Listenable 抽象类的子类。由于无法混入,所以通过继承来获得 Listenable 的能力,同时必须实现其 addListener / removeListener 方法,手动管理收听者。显然,这样太过复杂,我们通常都不需要这样做。

    class ChangeNotifier implements Listenable

    而混入了 ChangeNotifier 的类自动帮我们实现了听众管理,所以 ListenableProvider 同样也可以接收混入了 ChangeNotifier 的类。

    ChangeNotifierProvider 则更为简单,它能够对子节点提供一个 继承 / 混入 / 实现 了 ChangeNotifier 的类。通常我们只需要在 Model 中 with ChangeNotifier ,然后在需要刷新状态的时候调用 notifyListeners 即可。

    那么 ChangeNotifierProvider 和 ListenableProvider 究竟区别在哪呢,ChangeNotifierProvider 会在你需要的时候,自动调用其 _disposer 方法。

    static void _disposer(BuildContext context, ChangeNotifier notifier) => notifier?.dispose();

    我们可以在 Model 中重写 ChangeNotifier 的 dispose 方法,来释放其资源。

    ValueListenableProvider。

    ValueListenableProvider 用于提供实现了 继承 / 混入 / 实现 了 ValueListenable 的 Model。它实际上是专门用于处理只有一个单一变化数据的 ChangeNotifier。

    class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>

    通过 ValueListenable 处理的类不再需要数据更新的时候调用 notifyListeners。

    StreamProvider

    StreamProvider 专门用作提供(provide)一条 Single Stream。我在这里仅对其核心属性进行讲解。

    T initialData:你可以通过这个属性声明这条流的初始值。

    ErrorBuilder<T> catchError:这个属性用来捕获流中的 error。在这条流 addError 了之后,你会能够通过 T Function(BuildContext context, Object error) 回调来处理这个异常数据。实际开发中它非常有用。

    updateShouldNotify:和之前的回调一样,这里不再赘述。

    除了这三个构造方法都有的属性以外,StreamProvider 还有三种不同的构造方法。

    StreamProvider(...):默认构造方法用作创建一个 Stream 并收听它。

    StreamProvider.controller(...):通过 builder 方式创建一个 StreamController<T>。并且在 StreamProvider 被移除时,自动释放 StreamController。

    StreamProvider.value(...):监听一个已有的 Stream 并将其 value 提供给子孙节点。

    注意事项

    不要所有状态都放在全局

    开发者为了图方便省事,经常把所有东西都放在顶层 MaterialApp 之上。严格区分你的全局数据与局部数据,资源不用了就要释放!否则将会严重影响你的应用 performance。

    尽量在 Model 中使用私有变量“_”,减少耦合

    控制你的刷新范围

    在 Flutter 中,组合大于继承的特性随处可见。常见的 Widget 实际上都是由更小的 Widget 组合而成,直到基本组件为止。为了使我们的应用拥有更高的性能,控制 Widget 的刷新范围便显得至关重要。尽量使用 Consumer 来获取祖先 Model,以维持最小刷新范围。

    Provider 是如何做到状态共享的

    这个问题实际上得分两步。

    获取顶层数据

    实际上在祖先节点中共享数据这件事我们已经在之前的文章中接触过很多次了,都是通过系统的 InheritedWidget 进行实现的。Provider 也不例外,在所有 Provider 的 build 方法中,返回了一个 InheritedProvider。

    class InheritedProvider<T> extends InheritedWidget

    provider新版本sdk发生了变化,已经不能很明显的看出InheritedProvider和InheritedWidget的关系:

    class InheritedProvider<T> extends SingleChildStatelessWidget 
    abstract class InheritedWidget extends ProxyWidget 

    Flutter 通过在每个 Element 上维护一个 InheritedWidget 哈希表来向下传递 Element 树中的信息。通常情况下,多个 Element 引用相同的哈希表,并且该表仅在 Element 引入新的 InheritedWidget 时改变。时间复杂度为 O(1) 。

    通知刷新

    通知刷新这一步实际上就是使用了 Listener 模式。Model 中维护了一堆听众,然后 notifiedListener 通知刷新。

    全局状态需要放在顶层 MaterialApp 之上,优先初始化,以便在 Navigator 以及 BuildContex控制全局状态

    数据初始化

    全局数据

    当需要获取全局顶层数据,并需要做一些会产生额外结果的时候,main 函数是一个很好的选择。在 main 方法中创建 Model 并进行初始化的工作,这样就只会执行一次。

    单页面

    如果我们的数据只是在这个页面中需要使用,那么你有这两种方式可以选择。

    StatefulWidget

    在InitState()中使用 Provider.of<T>(context)是错误的。

      /// If [listen] is `true` (default), later value changes will trigger a new
      /// [State.build] to widgets, and [State.didChangeDependencies] for
      /// [StatefulWidget].

    源码中的注释解释了,如果这个 Provider.of<T>(context) listen 了的话,那么当 notifyListeners 的时候,就会触发 context 所对应的 State 的 [State.build] 和 [State.didChangeDependencies] 方法。也就是说,如果你使用了非 Provider 提供的数据,例如 ChangeNotifierProvider 这样会改变依赖的类,并且获取数据时 Provider.of<T>(context, listen: true) 选择 listen (默认就为 listen)的话,数据刷新时会重新运行 didChangeDependencies 和 build 两个方法。这样一来对 didChangeDependencies 也会产生副作用。假如在这里请求了数据,当数据到来的时候,又回触发下一次请求,最终无限请求下去。

    这里除了副作用以外还有一点,假如数据改变是一个同步行为,例如这里的 counter.increment 这样的方法,在 didChangeDependencies 中调用的话,就会造成下面这个错误。

    The following assertion was thrown while dispatching notifications for CounterModel:
    flutter: setState() or markNeedsBuild() called during build.
    flutter: This ChangeNotifierProvider<CounterModel> widget cannot be marked as needing to build because the
    flutter: framework is already in the process of building widgets. A widget can be marked as needing to be
    flutter: built during the build phase only if one of its ancestors is currently building. This exception is
    flutter: allowed because the framework builds parent widgets before children, which means a dirty descendant
    flutter: will always be built. Otherwise, the framework might not visit this widget during this build phase.

    这里和 Flutter 的构建算法有关。简单来说,就是不能够在 State 的 build 期间调用 setState() 或者 markNeedsBuild(),在我们这里 didChangeDependence 的时候调用了此方法,导致出现这个错误。异步数据则会由于 event loop 的缘故不会立即执行。

    首先 要保证初始化数据不能够产生副作用,我们需要找一个在 State 声明周期内一定只会运行一次的方法。initState 就是为此而生的。但是 initState 不是无法获取到 Inherit 吗。但是我们现在本身就在页面顶层啊,页面级别的 Model 就在顶层被创建,现在根本就不需要 Inherit。

    class _HomeState extends State<Home> {
        final _myModel = MyModel();
          @override
      void initState() {
        super.initState();
        _myModel.init(); 
      }
    }

    页面级别的 Model 数据都在页面顶层 Widget 创建并初始化即可。

     void initState() {
        super.initState();
        WidgetsBinding.instance.addPostFrameCallback((callback){
          Provider.of<CounterModel>(context).increment();
        });
      }

    我们通过 addPostFrameCallback 回调中在第一帧 build 结束时调用 increment 方法,这样就不会出现构建错误了。

    provider 作者 Remi 给出了另外一种方式

    This code is relatively unsafe. There's more than one reason for didChangeDependencies to be called.
    You probably want something similar to:

    MyCounter counter;
     
    @override
    void didChangeDependencies() {
      final counter = Provider.of<MyCounter>(context);
      if (conter != this.counter) {
        this.counter = counter;
        counter.increment();
      }
    }

    This should trigger increment only once.

    也就是说初始化数据之前判断一下这个数据是否已经存在。

    cascade
    你也可以在使用 dart 的级连语法 ..do() 直接在页面的 StatelessWidget 成员变量声明时进行初始化。

    class FirstScreen extends StatelessWidget {
        CounterModel _counter = CounterModel()..increment();
        double _textSize = 48;
        ...
    }


    使用这种方式需要注意,当这个 StatelessWidget 重新运行 build 的时候,状态会丢失。这种情况在 TabBarView 中的子页面切换过程中就可能会出现。

    性能问题

    遵守其规范,做任何事情都考虑对性能的影响,要知道 Flutter 把更新算法可是优化到了 O(N)。
    Provider 仅仅是对 InheritedWidget 的一个升级,不必担心引入 Provider 会对应用造成性能问题。

    为什么选择 Provider

    Provider 不仅做到了提供数据,而且它拥有着一套完整的解决方案,覆盖了你会遇到的绝大多数情况。
    但是仅仅使用 Provider,Model 和 View 之间还是容易产生依赖。

    只有通过手动将 Model 转化为 ViewModel 这样才能消除掉依赖关系,所以假如各位有组件化的需求,还需要另外处理。

    不过对于大多数情况来说,Provider 足以优秀,它能够让你开发出简单、高性能、层次清晰 的应用。
     

    源码分析

    Flutter 中的 Builder 模式

    在 Provider 中,各种 Provider 的原始构造方法都有一个 builder 参数,这里一般就用 (_) => XXXModel() 就行了。感觉有点多次一举,为什么不能像 .value() 构造方法那样简洁呢。

    实际上,Provider 为了帮我们管理 Model,使用到了 delegation pattern。

    builder 声明的 ValueBuilder 最终被传入代理类 BuilderStateDelegate / SingleValueDelegate。 然后通过代理类才实现的 Model 生命周期管理。

    class BuilderStateDelegate<T> extends ValueStateDelegate<T> {
      BuilderStateDelegate(this._builder, {Disposer<T> dispose})
          : assert(_builder != null),
            _dispose = dispose;
     
      final ValueBuilder<T> _builder;
      final Disposer<T> _dispose;
     
      T _value;
      @override
      T get value => _value;
     
      @override
      void initDelegate() {
        super.initDelegate();
        _value = _builder(context);
      }
     
      @override
      void didUpdateDelegate(BuilderStateDelegate<T> old) {
        super.didUpdateDelegate(old);
        _value = old.value;
      }
     
      @override
      void dispose() {
        _dispose?.call(context, value);
        super.dispose();
      }
    }

    更多相关内容
  • web3-provider

    2021-04-22 18:33:13
    该提供程序委派将所有签名方法发送到给定的提供程序(如果可用),并将所有其他方法发送给所提供的端点安装npm install ky # a peer dependencynpm install simple-web3-provider入门import Web3 from 'web3' ;...
  • sunjce_provider.jar

    2022-02-09 14:27:05
    jar包下载地址 : http://www.rsdown.cn/down/164019.html 下载后将sunjce_provider.jar放入webapp/WEB-INF/lib中
  • sunjce_provider.zip

    2021-08-12 16:50:20
    sunjce_provider.zip
  • 用于通过提供者私钥签署交易安装$ npm install truffle-privatekey-provider用法提供程序可以仅与Web3一起使用,也可以在Truffle基础结构中使用Web3的用法var PrivateKeyProvider = require ( "truffle-privatekey-...
  • FileProvider是ContentProvider特殊的子类,ContentProvider通过创建content:// Uri来替代file:/// Uri。 在Android 7.0的以上的系统中,尝试传递file://URI可能会触发FileUriExposedException FileProvider的这个...
  • 赠送jar包:fastjson-jaxrs-json-provider-0.3.1.jar; 赠送原API文档:fastjson-jaxrs-json-provider-0.3.1-javadoc.jar; 赠送源代码:fastjson-jaxrs-json-provider-0.3.1-sources.jar; 赠送Maven依赖信息文件:...
  • wagon-provider-api-2.9.jar

    2020-04-15 11:17:04
    wagon-provider-api-2.9.jar
  • apk文件 SettingsProvider(电视直播视频)apk文件 SettingsProvider(电视直播视频)apk文件 SettingsProvider(电视直播视频)apk文件 SettingsProvider(电视直播视频)apk文件 SettingsProvider(电视直播视频)...
  • 安装使用NPM npm install --save strapi-provider-upload-s3-plus 使用纱线yarn add strapi-provider-upload-s3-plus设置使用Strapi的插件config config/plugins.js启用提供程序module . exports = ( { env } ) => ...
  • IBM Data Server .NET Provider,安装以后,使用.net添加引用。 安装后的目录一般是在: C:\Program Files\IBM\IBM DATA SERVER DRIVER\bin 下面 。添加引用以后就可以连接DB2了。 明细见: ...因网络不好,上传时可能...
  • sunjce_provider.jar
  • sunjce_provider-1.0.0.jar

    2018-07-05 16:29:18
    Solving the problem was a matter of putting the sunjce_provider.jar in one of the folders specified in the java.ext.dirs parameter. Hard to find, easy to solve. 把sunjce_provider.jar 放到JDKclasspath...
  • 本章节主要讲述如何配置dubbo,按照配置方式上分,可以...按照功能角度进行划分,可以分为DubboProvider和DubboConsumer。接下来章节中,分别对dubbo provider和Dubboconsumer进行讲解。 配置DubboProvider有4种方式:
  • Android 中 子moudle中provider和主工程中provider冲突Android 中 子moudle中provider和主工程中provider冲突Android 中 子moudle中provider和主工程中provider冲突
  • 用于连接db2数据库的驱动,
  • Java加密解密字符串找不到 com.sun.crypto.provider.SunJCE() 用到jar包,将包放入lib目录,build path引入,即可
  • trezor-web3-provider 启用Trezor的Truffle Web3提供程序。 使用它通过Trezor硬件钱包签署交易 安装 $ npm install trezor-web3-provider 一般用法 您可以使用此web3提供程序通过Trezor硬件钱包签署交易 var ...
  • Android:Content Provider的使用。 1、Content Provider 简介 2、使用现成的Content Provider 3、定义自己的Content Provider 一、Content Provider 简介 我们说Android应用程序的四个核心组件是:Activity、...
  • SQLServerProvider_Vc_

    2021-10-04 05:23:47
    使用 OLEDB 建立统一的数据访问平台VC源代码
  • new com.sun.crypto.provider.SunJCE()找不到,需要导入jar包,而这个jar包在高版本的jdk里面已经找不到了,只有jdk1.6_13里面才有。提供出来。
  • JAVA数据加密jar包之sunjce_provider.rar,sunjce_provider.jar
  • 注册血管提供者 使用 vCenter SDK 将 VASA Provider 注册到 vCenter
  • 在与银联的对接中,调试过程中报错或使用类似登入加密:java.lang.SecurityException: JCE cannot authenticate the provider BC 进行问题解决,里面包含 bcprov-jdk16-143.jar与bcprov-jdk15-135.jar与具体文件存放...
  • FileProvider使用demo

    2015-10-19 14:13:13
    http://blog.csdn.net/laxian2009/article/details/49249305
  • Flutter provider状态管理框架
  • 采用这个引擎可以打开Provider=Microsoft.ACE.OLEDB.12.0;Data Source= 来读取access 2003,2007版本
  • 数据提供者它是一个帮助在 Android 应用程序中编写数据驱动的单元测试用例的库...@Source ( fileName = " testcases.xls " )public class SampleTest extends DataProviderTestCase {@DataProvider ( label = " test1

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 561,500
精华内容 224,600
关键字:

provider