Flutter Provider 嵌套对象

本文介绍了Flutter Provider 嵌套对象的处理方法,对大家解决问题具有一定的参考价值

问题描述

我正在使用 Provider Package 来管理我的 Flutter 应用程序中的状态.当我开始嵌套我的对象时,我遇到了问题.

I'm using the Provider Package to manage state in my Flutter App. I am running into issues when I start nesting my objects.

一个非常简单的例子:父 A 有 B 类型的子,B 有 C 类型的子,C 有 D 类型的子.在子 D 中,我想管理一个颜色属性.下面的代码示例:

A very simple example: Parent A has child of type B, which has child of type C, which has child of type D. In child D, I want to manage a color attribute. Code example below:

import 'package:flutter/material.dart';

class A with ChangeNotifier
{
    A() {_b = B();}

    B _b;
    B get b => _b;

    set b(B value)
    {
        _b = value;
        notifyListeners();
    }
}

class B with ChangeNotifier
{
    B() {_c = C();}

    C _c;
    C get c => _c;

    set c(C value)
    {
        _c = value;
        notifyListeners();
    }
}

class C with ChangeNotifier
{
    C() {_d = D();}

    D _d;
    D get d => _d;

    set d(D value)
    {
        _d = value;
        notifyListeners();
    }
}

class D with ChangeNotifier
{
    int                 _ColorIndex = 0;
    final List<Color>   _ColorList = [
        Colors.black,
        Colors.blue,
        Colors.green,
        Colors.purpleAccent
    ];

    D()
    {
        _color = Colors.red;
    }

    void ChangeColor()
    {
        if(_ColorIndex < _ColorList.length - 1)
        {
            _ColorIndex++;
        }
        else
        {
            _ColorIndex = 0;
        }

        color = _ColorList[_ColorIndex];
    }

    Color _color;

    Color get color => _color;

    set color(Color value)
    {
        _color = value;
        notifyListeners();
    }
}

现在我的 ma​​in.dart(管理我的 Placeholder() 小部件)包含以下内容:

Now my main.dart (which is managing my Placeholder() widget) contains the following:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/NestedObjects.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget
{
    @override
    Widget build(BuildContext context)
    {
        return MaterialApp(
            home: ChangeNotifierProvider<A>(
                builder: (context) => A(),
                child: MyHomePage()
            ),
        );
    }
}

class MyHomePage extends StatefulWidget
{

    @override
    State createState()
    {
        return _MyHomePageState();
    }
}

class _MyHomePageState extends State<MyHomePage>
{
    @override
    Widget build(BuildContext context)
    {
        A   a = Provider.of<A>(context);
        B   b = a.b;
        C   c = b.c;
        D   d = c.d;

        return Scaffold(
            body: Center(
                child: Column(
                    children: <Widget>[
                        Text(
                            'Current selected Color',
                        ),
                        Placeholder(color: d.color,),
                    ],
                ),
            ),
            floatingActionButton: FloatingActionButton(
                onPressed: () => ButtonPressed(context),
                tooltip: 'Increment',
                child: Icon(Icons.arrow_forward),
            ),
        );
    }

    void ButtonPressed(BuildContext aContext)
    {
        A   a = Provider.of<A>(context);
        B   b = a.b;
        C   c = b.c;
        D   d = c.d;

        d.ChangeColor();
    }
}

以上说明Placeholder Widget的color属性是由Class D的color属性(A -> B -> C定义的-> D.color).上面的代码非常简化,但它确实显示了我遇到的问题.

The above shows that the Placeholder Widget's color attribute is defined by Class D's color property (A -> B -> C -> D.color). The above code is extremely simplified, but it does show the issue I'm having.

回到正题:如何将child D的颜色属性分配给一个小部件,以便在更新child D时s 属性,它还会自动更新小部件(使用 notifyListeners(),而不是 setState()).

Back to the point: how would I assign child D's color property to a widget, so that when updating child D's property, it also automatically updates the widget (using notifyListeners(), not setState()).

我使用过 StatelessStatefulProvider.ofConsumer,所有这些都给了我同样的结果.重申一下,对象不能解耦,必须有父子关系.

I've used Stateless, Stateful, Provider.of and Consumer, all which gives me the same result. Just to reiterate, the objects can't be decoupled, it has to have parent-child relationships.

编辑

更复杂的例子:

import 'dart:ui';

enum Manufacturer
{
    Airbus, Boeing, Embraer;
}

class Fleet
{
    List<Aircraft> Aircrafts;
}

class Aircraft
{
    Manufacturer        AircraftManufacturer;
    double              EmptyWeight;
    double              Length;
    List<Seat>          Seats;
    Map<int,CrewMember> CrewMembers;
}

class CrewMember
{
    String Name;
    String Surname;
}

class Seat
{
    int     Row;
    Color   SeatColor;
}

以上代码是真实世界示例的简化版本.正如你可以想象的那样,兔子洞可以越来越深.所以,我所说的 AD 示例的意思是试图简化情况的卷积.

The above code is a simplified version of a real world example. As you can imagine the rabbit hole can go deeper and deeper. So, what I meant by the A through D example was trying to simplify the convolution of the situation.

例如,假设您想在小部件中显示和/或更改机组成员的姓名.在应用程序本身中,您通常会从 Fleet 中选择一个 Aircraft(通过 List 索引传递给小部件),然后选择一个 CrewMember 来自 Aircraft(通过 Map 键),然后显示/更改 CrewMemberName.

Lets say for example you want to display and/or change a crew members' name in a widget. In the app itself you would typically select an Aircraft from the Fleet (passed to widget by List index), then select a CrewMember from the Aircraft (passed by Map key) and then display/change the Name of CrewMember.

最后,您的小部件将能够通过使用传入的 Aircrafts 索引和 CrewMembers 键来查看您所指的机组成员的姓名.

In the end your widget will be able to see what Crew Member's name you are referring to by using the passed in Aircrafts index and CrewMembers key.

我绝对愿意接受更好的架构和设计.

I'm definitely open to a better architecture and designs.

推荐答案

更新问题的答案,原文如下

在您最初的问题中,不清楚 ABCD 代表什么.原来那些是模型.

It was not clear what A, B, C and D stood for in your original question. Turns out those were models.

我目前的想法是,用 MultiProvider/ProxyProvider 包装您的应用程序以提供 服务,而不是模型.

My current thinking is, wrap your app with MultiProvider/ProxyProvider to provide services, not models.

不确定您是如何加载数据的(如果有的话),但我假设有一项服务可以异步获取您的队列.如果您的数据由部件/模型通过不同的服务加载(而不是一次全部加载),您可以将它们添加到 MultiProvider 并在需要加载更多数据时将它们注入适当的小部件中.

Not sure how you are loading your data (if at all) but I assumed a service that asynchronously fetches your fleet. If your data is loaded by parts/models through different services (instead of all at once) you could add those to the MultiProvider and inject them in the appropriate widgets when you need to load more data.

下面的示例功能齐全.为了简单起见,并且由于您以更新 name 为例,我只制作了该属性设置器 notifyListeners().

The example below is fully functional. For the sake of simplicity, and since you asked about updating name as an example, I only made that property setter notifyListeners().

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

main() {
  runApp(
    MultiProvider(
      providers: [Provider.value(value: Service())],
      child: MyApp()
    )
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Consumer<Service>(
            builder: (context, service, _) {
              return FutureBuilder<Fleet>(
                future: service.getFleet(), // might want to memoize this future
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    final member = snapshot.data.aircrafts[0].crewMembers[1];
                    return ShowCrewWidget(member);
                  } else {
                    return CircularProgressIndicator();
                  }
                }
              );
            }
          ),
        ),
      ),
    );
  }
}

class ShowCrewWidget extends StatelessWidget {

  ShowCrewWidget(this._member);

  final CrewMember _member;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CrewMember>(
      create: (_) => _member,
      child: Consumer<CrewMember>(
        builder: (_, model, __) {
          return GestureDetector(
            onDoubleTap: () => model.name = 'Peter',
            child: Text(model.name)
          );
        },
      ),
    );
  }
}

enum Manufacturer {
    Airbus, Boeing, Embraer
}

class Fleet extends ChangeNotifier {
    List<Aircraft> aircrafts = [];
}

class Aircraft extends ChangeNotifier {
    Manufacturer        aircraftManufacturer;
    double              emptyWeight;
    double              length;
    List<Seat>          seats;
    Map<int,CrewMember> crewMembers;
}

class CrewMember extends ChangeNotifier {
  CrewMember(this._name);

  String _name;
  String surname;

  String get name => _name;
  set name(String value) {
    _name = value;
    notifyListeners();
  }

}

class Seat extends ChangeNotifier {
  int row;
  Color seatColor;
}

class Service {

  Future<Fleet> getFleet() {
    final c1 = CrewMember('Mary');
    final c2 = CrewMember('John');
    final a1 = Aircraft()..crewMembers = { 0: c1, 1: c2 };
    final f1 = Fleet()..aircrafts.add(a1);
    return Future.delayed(Duration(seconds: 2), () => f1);
  }

}

运行应用程序,等待 2 秒以加载数据,您应该会在该地图中看到 id=1 的机组成员John".然后双击文本,它应该会更新为Peter".

Run the app, wait 2 seconds for data to load, and you should see "John" which is crew member with id=1 in that map. Then double-tap the text and it should update to "Peter".

如您所见,我使用的是服务的顶级注册(Provider.value(value: Service()))和模型的本地注册(ChangeNotifierProviderCrewMember>(创建:...)).

As you can notice, I am using top-level registering of services (Provider.value(value: Service())), and local-level registering of models (ChangeNotifierProvider<CrewMember>(create: ...)).

我认为这种架构(具有合理数量的模型)应该是可行的.

I think this architecture (with a reasonable amount of models) should be feasible.

关于本地级别的提供程序,我觉得它有点冗长,但可能有办法让它更短.此外,为带有 setter 的模型提供一些代码生成库来通知更改会很棒.

Regarding the local-level provider, I find it a bit verbose, but there might be ways to make it shorter. Also, having some code generation library for models with setters to notify changes would be awesome.

(您有 C# 背景吗?我已将您的类修复为符合 Dart 语法.)

(Do you have a C# background? I fixed your classes to be in line with Dart syntax.)

让我知道这是否适合你.

Let me know if this works for you.

如果您想使用 Provider,则必须使用 Provider 构建依赖关系图.

If you want to use Provider you'll have to build the dependency graph with Provider.

(你可以选择构造函数注入,而不是setter注入)

(You could choose constructor injection, instead of setter injection)

这行得通:

main() {
  runApp(MultiProvider(
    providers: [
        ChangeNotifierProvider<D>(create: (_) => D()),
        ChangeNotifierProxyProvider<D, C>(
          create: (_) => C(),
          update: (_, d, c) => c..d=d
        ),
        ChangeNotifierProxyProvider<C, B>(
          create: (_) => B(),
          update: (_, c, b) => b..c=c
        ),
        ChangeNotifierProxyProvider<B, A>(
          create: (_) => A(),
          update: (_, b, a) => a..b=b
        ),
      ],
      child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(context) {
    return MaterialApp(
      title: 'My Flutter App',
      home: Scaffold(
          body: Center(
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                      Text(
                          'Current selected Color',
                      ),
                      Consumer<D>(
                        builder: (context, d, _) => Placeholder(color: d.color)
                      ),
                  ],
              ),
          ),
          floatingActionButton: FloatingActionButton(
              onPressed: () => Provider.of<D>(context, listen: false).color = Colors.black,
              tooltip: 'Increment',
              child: Icon(Icons.arrow_forward),
          ),
      ),
    );
  }
}

此应用基于您的 ABCD 类运行.

This app works based on your A, B, C and D classes.

您的示例不使用代理,因为它只使用没有依赖关系的 D.但是你可以看到 Provider 已经通过这个例子正确地连接了依赖项:

Your example does not use proxies as it only uses D which has no dependencies. But you can see Provider has hooked up dependencies correctly with this example:

Consumer<A>(
  builder: (context, a, _) => Text(a.b.c.d.runtimeType.toString())
),

它会打印出D".

ChangeColor() 不起作用,因为它没有调用 notifyListeners().

ChangeColor() did not work because it is not calling notifyListeners().

没有必要在此之上使用有状态小部件.

There is no need to use a stateful widget on top of this.

这篇关于Flutter Provider 嵌套对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2

admin_action_{$_REQUEST[‘action’]}

do_action( "admin_action_{$_REQUEST[‘action’]}" )动作钩子::在发送“Action”请求变量时激发。Action Hook: Fires when an ‘action’ request variable is sent.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$_REQUEST['action']引用从GET或POST请求派生的操作。源码(Source)更新版本源码位置使用被使用2.6.0 wp-admin/admin.php:...

日期:2020-09-02 17:44:16 浏览:1171

admin_footer-{$GLOBALS[‘hook_suffix’]}

do_action( "admin_footer-{$GLOBALS[‘hook_suffix’]}", string $hook_suffix )操作挂钩:在默认页脚脚本之后打印脚本或数据。Action Hook: Print scripts or data after the default footer scripts.目录锚点:#说明#参数#源码说明(Description)钩子名的动态部分,$GLOBALS['hook_suffix']引用当前页的全局钩子后缀。参数(Parameters)参数类...

日期:2020-09-02 17:44:20 浏览:1071

customize_save_{$this->id_data[‘base’]}

do_action( "customize_save_{$this-&gt;id_data[‘base’]}", WP_Customize_Setting $this )动作钩子::在调用WP_Customize_Setting::save()方法时激发。Action Hook: Fires when the WP_Customize_Setting::save() method is called.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_data...

日期:2020-08-15 15:47:24 浏览:808

customize_value_{$this->id_data[‘base’]}

apply_filters( "customize_value_{$this-&gt;id_data[‘base’]}", mixed $default )过滤器::过滤未作为主题模式或选项处理的自定义设置值。Filter Hook: Filter a Customize setting value not handled as a theme_mod or option.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_date['base'],指的是设置...

日期:2020-08-15 15:47:24 浏览:900

get_comment_author_url

过滤钩子:过滤评论作者的URL。Filter Hook: Filters the comment author’s URL.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/comment-template.php:32610...

日期:2020-08-10 23:06:14 浏览:930

network_admin_edit_{$_GET[‘action’]}

do_action( "network_admin_edit_{$_GET[‘action’]}" )操作挂钩:启动请求的处理程序操作。Action Hook: Fires the requested handler action.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$u GET['action']引用请求的操作的名称。源码(Source)更新版本源码位置使用被使用3.1.0 wp-admin/network/edit.php:3600...

日期:2020-08-02 09:56:09 浏览:877

network_sites_updated_message_{$_GET[‘updated’]}

apply_filters( "network_sites_updated_message_{$_GET[‘updated’]}", string $msg )筛选器挂钩:在网络管理中筛选特定的非默认站点更新消息。Filter Hook: Filters a specific, non-default site-updated message in the Network admin.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分$_GET['updated']引用了非默认的...

日期:2020-08-02 09:56:03 浏览:865

pre_wp_is_site_initialized

过滤器::过滤在访问数据库之前是否初始化站点的检查。Filter Hook: Filters the check for whether a site is initialized before the database is accessed.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/ms-site.php:93910...

日期:2020-07-29 10:15:38 浏览:834

WordPress 的SEO 教学:如何在网站中加入关键字(Meta Keywords)与Meta 描述(Meta Description)?

你想在WordPress 中添加关键字和meta 描述吗?关键字和meta 描述使你能够提高网站的SEO。在本文中,我们将向你展示如何在WordPress 中正确添加关键字和meta 描述。为什么要在WordPress 中添加关键字和Meta 描述?关键字和说明让搜寻引擎更了解您的帖子和页面的内容。关键词是人们寻找您发布的内容时,可能会搜索的重要词语或片语。而Meta Description则是对你的页面和文章的简要描述。如果你想要了解更多关于中继标签的资讯,可以参考Google的说明。Meta 关键字和描...

日期:2020-10-03 21:18:25 浏览:1733

谷歌的SEO是什么

SEO (Search Engine Optimization)中文是搜寻引擎最佳化,意思近于「关键字自然排序」、「网站排名优化」。简言之,SEO是以搜索引擎(如Google、Bing)为曝光媒体的行销手法。例如搜寻「wordpress教学」,会看到本站的「WordPress教学:12个课程…」排行Google第一:关键字:wordpress教学、wordpress课程…若搜寻「网站架设」,则会看到另一个网页排名第1:关键字:网站架设、架站…以上两个网页,每月从搜寻引擎导入自然流量,达2万4千:每月「有机搜...

日期:2020-10-30 17:23:57 浏览:1309