Flutter Provider 嵌套对象 [英] Flutter Provider Nested Objects

查看:27
本文介绍了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 嵌套对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆