Flutter使用RepositoryProvider解决跨组件传值问题

 更新时间:2022年04月02日 11:52:46   作者:岛上码农  
在实际开发过程中,经常会遇到父子组件传值的情况。本文将利用RepositoryProvider解决跨组件传值的问题,感兴趣的小伙伴可以了解一下

前言

在实际开发过程中,经常会遇到父子组件传值的情况,通常来说会有三种方式:

  • 构造函数传值:父组件将子组件需要的对象通过构造函数传递给子组件;
  • 单例对象:构建单例对象,使得父子组件使用的是同一个对象;
  • 容器:将对象存入容器中,父子组件使用的时候直接从容器中获取。

第一种方式的缺陷是如果组件嵌套很深,传递数据对象需要层层传递,将导致代码很难维护。第二种方式需要自己构建单例类,而实际上要传递的对象可能存在很多个实例。第三种和单例类似,如果往容器存储不定数量的实例对象是不合适的。flutter_bloc 提供了一种基于组件的依赖注入方式解决这类问题,通过使用 RepositoryProvider,可以为组件树的子组件提供共享对象,这个共享对象只限在组件树中使用,可以通过 Provider 的方式访问该对象。

RepositoryProvider定义

Repository 实际上是 Provider 的一个子类,通过注册单例的方式实现组件树对象共享,因此其注册的对象会随着 Provider 的注销而销毁,而且这个对象无需是 Bloc 子类。因此在无法使用 Bloc 传输共享对象的时候,可以使用 RepositoryProvider 来完成。RepositoryProvider有两种方式创建对象共享,create 和 value 方式,其中 create 是通过调用一个方法创建新的对象,而 value 是共享一个已有的对象。RepositoryProvider的定义如下:

class RepositoryProvider<T> extends Provider<T>
    with RepositoryProviderSingleChildWidget {
  RepositoryProvider({
    Key? key,
    required Create<T> create,
    Widget? child,
    bool? lazy,
  }) : super(
          key: key,
          create: create,
          dispose: (_, __) {},
          child: child,
          lazy: lazy,
        );


  RepositoryProvider.value({
    Key? key,
    required T value,
    Widget? child,
  }) : super.value(
          key: key,
          value: value,
          child: child,
        );
  
  static T of<T>(BuildContext context, {bool listen = false}) {
    try {
      return Provider.of<T>(context, listen: listen);
    } on ProviderNotFoundException catch (e) {
      if (e.valueType != T) rethrow;
      throw FlutterError(
        '''
        RepositoryProvider.of() called with a context that does not contain a repository of type $T.
        No ancestor could be found starting from the context that was passed to RepositoryProvider.of<$T>().

        This can happen if the context you used comes from a widget above the RepositoryProvider.

        The context used was: $context
        ''',
      );
    }
  }
}

RepositoryProviderSingleChildWidget本身是一个空的 Mixin:

mixin RepositoryProviderSingleChildWidget on SingleChildWidget {}

,注释上写着其用途是为了方便 MultiRepositoryProvider推断RepositoryProvider的类型设计。可以看到实际上 RepositoryProvider就是 Provider,只是将静态方法 of 的listen 参数默认设置为 false 了,也就是不监听状态对象的变化。我们在子组件中通过两种方式访问共享对象:

// 方式1
context.read<T>()
// 方式2
RepositoryProvider.of<T>(context)

如果有多个对象需要共享,可以使用MultiRepositoryProvider,使用方式也和 MultiProvider 相同 :

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

RepositoryProvider 应用

回顾一下我们之前使用 BlocBuilder 仿掘金个人主页的代码,在里面我们页面分成了三个部分:

  • 头像及背景图:_getBannerWithAvatar
  • 个人资料:_getPersonalProfile
  • 个人数据统计:_getPersonalStatistic

分别使用了三个构建组件的函数完成。对应的界面如下所示:

PersonalEntity personalProfile = personalResponse.personalProfile!;
        return Stack(
          children: [
            CustomScrollView(
              slivers: [
                _getBannerWithAvatar(context, personalProfile),
                _getPersonalProfile(personalProfile),
                _getPersonalStatistic(personalProfile),
              ],
            ),
            // ...
          ],
        );
      },
//...

可以看到,每个函数都需要把 personalProfile 这个对象通过函数的参数传递,而如果函数中的组件还有下级组件需要这个对象,还需要继续往下传递。这要是需要修改对象传值的方式,需要沿着组件树逐级修改,维护起来会很不方便。我们改造一下,将三个函数构建组件分别换成自定义的 Widget,并且将个人统计区换成两级组件,改造后的组件树如下所示(省略了装饰类的层级)。

组件层级

拆解完之后,我们就可以简化personalProfile 的传值了。

RepositoryProvider.value(
  child: CustomScrollView(
    slivers: [
      const BannerWithAvatar(),
      const PersonalProfile(),
      const PersonalStatistic(),
    ],
  ),
  value: personalProfile,
),
// ...

这里使用value模式是因为 personalProfile 已经被创建了。然后在需要使用 personalProfile 的地方,使用context.read<PersonalEntity>()就可以从 RepositoryProvider 中取出personalProfile对象了,从而使得各个子组件无需再传递该对象。以BannerWithAvatar 为例,如下所示:

class BannerWithAvatar extends StatelessWidget {
  final double bannerHeight = 230;
  final double imageHeight = 180;
  final double avatarRadius = 45;
  final double avatarBorderSize = 4;

  const BannerWithAvatar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter(
      child: Container(
        height: bannerHeight,
        color: Colors.white70,
        alignment: Alignment.topLeft,
        child: Stack(
          children: [
            Container(
              height: bannerHeight,
            ),
            Positioned(
              top: 0,
              left: 0,
              child: CachedNetworkImage(
                imageUrl:
                    'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=688497718,308119011&fm=26&gp=0.jpg',
                height: imageHeight,
                width: MediaQuery.of(context).size.width,
                fit: BoxFit.fill,
              ),
            ),
            Positioned(
              left: 20,
              top: imageHeight - avatarRadius - avatarBorderSize,
              child: _getAvatar(
                context.read<PersonalEntity>().avatar,
                avatarRadius * 2,
                avatarBorderSize,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _getAvatar(String avatarUrl, double size, double borderSize) {
    return Stack(alignment: Alignment.center, children: [
      Container(
        width: size + borderSize * 2,
        height: size + borderSize * 2,
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(size / 2 + borderSize),
        ),
      ),
      Container(
        width: size,
        height: size,
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          color: Colors.black,
          borderRadius: BorderRadius.circular(size / 2),
        ),
        child: CachedNetworkImage(
          imageUrl: avatarUrl,
          height: size,
          width: size,
          fit: BoxFit.fill,
        ),
      ),
    ]);
  }
}

可以看到整个代码更简洁也更易于维护了。

总结

本篇介绍了 RepositoryProvider 的使用,实际上 RepositoryProvider 借用Provider 实现了一个组件树上的局部共享对象容器。通过这个容器,为RepositoryProvider的子组件树注入了共享对象,使得子组件可以从 context 中或使用RepositoryProvider.of 静态方法获取共享对象。通过这种方式避免了组件树的层层传值,使得代码更为简洁和易于维护。

以上就是Flutter使用RepositoryProvider解决跨组件传值问题的详细内容,更多关于Flutter跨组件传值的资料请关注脚本之家其它相关文章!

相关文章

  • Android中ListActivity用法实例分析

    Android中ListActivity用法实例分析

    这篇文章主要介绍了Android中ListActivity用法,结合实例形式较为详细的分析了ListActivity功能,注意事项与相关使用技巧,需要的朋友可以参考下
    2016-02-02
  • Android IPC机制利用Messenger实现跨进程通信

    Android IPC机制利用Messenger实现跨进程通信

    这篇文章主要介绍了Android IPC机制中 Messager 实现跨进程通信的知识,对Android学习通信知识非常重要,需要的同学可以参考下
    2016-07-07
  • Android音视频开发之MediaCodec的使用教程

    Android音视频开发之MediaCodec的使用教程

    在Android开发中提供了实现音视频编解码工具MediaCodec,针对对应音视频解码类型通过该类创建对应解码器就能实现对数据进行解码操作。本文通过示例详细讲解了MediaCodec的使用,需要的可以参考一下
    2022-04-04
  • 怎么发布打包并发布自己的Android应用(APP)

    怎么发布打包并发布自己的Android应用(APP)

    前面我为大家讲的都是关于Android开发方面的知识点和技术,不少朋友可能会感到疑惑--究竟我该怎么打包、发布自己开发的APP,怎样将我的APP放到网上工别人下载,怎样保证我的APP安全及版权问题呢
    2013-11-11
  • Android仿微信实现下拉列表

    Android仿微信实现下拉列表

    这篇文章主要介绍了Android仿微信实现下拉列表的相关资料,需要的朋友可以参考下
    2015-12-12
  • 浅谈Android App开发中Fragment的创建与生命周期

    浅谈Android App开发中Fragment的创建与生命周期

    这篇文章主要介绍了Android App开发中Fragment的创建与生命周期,文中详细地介绍了Fragment的概念以及一些常用的生命周期控制方法,需要的朋友可以参考下
    2016-02-02
  • Android如何自定义EditText下划线?

    Android如何自定义EditText下划线?

    Android如何自定义EditText下划线?本文教大家利用Android实现自定义的EditText下划线,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • Android高仿微信对话列表滑动删除效果

    Android高仿微信对话列表滑动删除效果

    这篇文章主要为大家详细介绍了Android高仿微信对话列表滑动删除效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • Android CameraX结合LibYUV和GPUImage自定义相机滤镜

    Android CameraX结合LibYUV和GPUImage自定义相机滤镜

    之前使用Camera实现了一个自定义相机滤镜(Android自定义相机滤镜 ),但是运行起来有点卡顿,这次用Camerax来实现一样的效果发现很流畅,在此记录一下,也希望能帮到有需要的同学
    2021-12-12
  • Flutter的键值存储数据库使用示例详解

    Flutter的键值存储数据库使用示例详解

    这篇文章主要为大家介绍了Flutter的键值存储数据库使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08

最新评论