Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

In the actual development process, it is often encountered that the parent and child components pass values, generally speaking, there are three ways:

  • Constructor pass: The parent passes objects needed by the child to the child through the constructor;
  • Singletons: Build singletons so that parent and child components use the same object;
  • Container: Objects are stored in a container and retrieved directly from the container when used by parent and child components.

The drawback of the first approach is that if components are deeply nested, passing data objects requires layers of passing, which makes the code difficult to maintain. The second approach requires you to build your own singleton class, when in reality there may be many instances of the object being passed. The third is similar to singletons in that it is not appropriate to store an indefinite number of instance objects into a container. Flutter_bloc provides a component-based dependency injection solution to this problem by using RepositoryProvider to provide a shared object to a child of the component tree, which is only available within the component tree and can be accessed via the Provider.

RepositoryProvider definition

Repository is actually a subclass of Provider that shares component tree objects by registering singletons, so the registered object is destroyed when Provider is unregistered and does not need to be a Bloc subclass. Therefore, if Bloc cannot be used to transfer shared objects, RepositoryProvider can be used to do so. RepositoryProvider RepositoryProvider has two ways to create an object share, create or value, where CREATE creates a new object by calling a method and value shares an existing object. RepositoryProvider is defined as follows:

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' ' ',); }}}Copy the code

Mixins RepositoryProviderSingleChildWidget itself is an empty:

mixin RepositoryProviderSingleChildWidget on SingleChildWidget {}
Copy the code

The comment says its purpose is to make it easier for the MultiRepositoryProvider to infer the type design of the RepositoryProvider. RepositoryProvider is a Provider, except that the listen parameter of the static method of is set to false by default, which means that the status object is not monitored for changes. We access shared objects in child components in two ways:

1 / / way
context.read<T>()
2 / / way
RepositoryProvider.of<T>(context)
Copy the code

If you have multiple objects to share, you can use MultiRepositoryProvider in the same way:

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

RepositoryProvider application

Take a look back at the code we used to mock the Gold Digger homepage with BlocBuilder, where we split the page into three sections:

  • Head picture and background picture:_getBannerWithAvatar;
  • Personal Information:_getPersonalProfile;
  • Personal Data Statistics:_getPersonalStatistic.

This is done using three separate build component functions. The corresponding interface is as follows:

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

As you can see, each function needs to pass the personalProfile object as an argument to the function, and further down if the component in the function has a child component that needs the object. If you need to change the way an object is passed, you need to change it step by step down the component tree, which can be very difficult to maintain. Instead of replacing the three function building components with custom widgets, and replacing the personal statistics area with two levels of components, we transformed the component tree as shown below (omitting the decorator class hierarchy).

With that done, we can simplify the pass-through of the personalProfile.

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

The value pattern is used here because the personalProfile has already been created. The personalProfile object can then be retrieved from the RepositoryProvider where the personalProfile is needed, using Context.read (). This eliminates the need for child components to pass the object. Take BannerWithAvatar as an example, as follows:

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, & FM = 26 & gp = 0. 308119011 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, ), ), ]); }}Copy the code

You can see that the whole code is much cleaner and easier to maintain. The specific source code has been uploaded to: BLoC State Management code, you can download it for reference.

conclusion

This article describes the use of RepositoryProvider, which essentially borrows the Provider to implement a locally shared object container in the component tree. The container is used to inject shared objects into the RepositoryProvider child tree so that the child can get the shared objects from the context or using the repositoryProvider.of static method. In this way, layer by layer values in the component tree are avoided, making the code simpler and easier to maintain.

I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.

👍🏻 : feel the harvest please point a praise to encourage!

🌟 : Collect articles, easy to look back!

💬 : Comment exchange, mutual progress!