Flutter Provider 嵌套对象

标签 flutter dart widget provider

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

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

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();
    }
}

现在我的 main.dart (管理我的 Placeholder() 小部件)包含以下内容:
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();
    }
}


以上显示占位符小工具 的颜色属性由 定义D类的颜色属性(A -> B -> C -> D.color) .上面的代码非常简化,但它确实显示了我遇到的问题。

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

我用过无国籍 , 有状态的 , 提供者.of 消费者 ,所有这些都给了我相同的结果。重申一下,对象不能解耦,它必须具有父子关系。

编辑

更复杂的例子:
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;
}

上面的代码是真实世界示例的简化版本。正如你可以想象的那样,兔子洞可以越来越深。那么,我所说的 A 是什么意思?通过D例如试图简化情况的卷积。

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

最后,您的小部件将能够通过使用传入的 Aircrafts 来查看您所指的船员姓名。索引和 CrewMembers key 。

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

最佳答案

编辑:回答更新后的问题,原文如下

不清楚是什么A , B , CD代表你原来的问题。原来那些是模型。

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

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

下面的示例功能齐全。为简单起见,既然您询问了有关更新 name 的问题举个例子,我只做了那个属性 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 秒以加载数据,您应该会在该 map 中看到 id=1 的机组成员“John”。然后双击文本,它应该更新为“Peter”。

如您所见,我使用的是服务的顶级注册( Provider.value(value: Service()) )和模型的本地级别注册( ChangeNotifierProvider<CrewMember>(create: ...) )。

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

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

(你有 C# 背景吗?我修复了你的类以符合 Dart 语法。)

让我知道这是否适合您。

如果要使用 Provider,则必须使用 Provider 构建依赖关系图。

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

这有效:

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),
          ),
      ),
    );
  }
}

此应用程序基于您的 A 工作, B , CD类。

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

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

它将打印出“D”。
ChangeColor()没有工作,因为它没有调用 notifyListeners() .

无需在此之上使用有状态小部件。

关于Flutter Provider 嵌套对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59375130/

相关文章:

http - 状态错误 : Cannot set the body fields of a Request with content-type "application/json"

dart - 在Polymer.dart中访问父上下文

python - 更改 ttk 小部件文本颜色

firebase - 使用Flutter在Firebase中使用Stream <QuerySnapshot>检索数据

flutter : How to set fix width of Bar chart Column with scrolling x axis

flutter - 如何检测ListView项目是否在屏幕外添加?

flutter - Flutter:如何在Firestore中获取阵列数据?

build - flutter 的iOS构建失败(ld:未找到-lPods-Runner的库)

select - Bokeh :初始创建后更新小部件

extjs - 切换对话框中一组字段的可见性