Flutter提供程序嵌套对象 [英] Flutter Provider Nested Objects

查看:154
本文介绍了Flutter提供程序嵌套对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用提供商包来管理我的Flutter应用中的状态。我开始嵌套对象时遇到了问题。



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

  import‘package:flutter / material.dart’; 

具有ChangeNotifier的A类
{
A(){_b = B();}

B _b;
B得到b => _b;

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

具有ChangeNotifier的B类
{
B(){_c = C();}

C _c;
C get c => _C;

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

C类,带有ChangeNotifier
{
C(){_d = D();}

D _d;
D得到d => _d;

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

D类,带有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 ++;
}
其他
{
_ColorIndex = 0;
}

color = _ColorList [_ColorIndex];
}

颜色_color;

颜色获取颜色=> _颜色;

设置颜色(颜色值)
{
_color = value;
notifyListeners();
}
}

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

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

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

类MyApp扩展了StatelessWidget
{
@override
Widget build(BuildContext context)
{
return MaterialApp(
home :ChangeNotifierProvider< A>(
生成器:(上下文)=> A(),
子级:MyHomePage()
),
);
}
}

类MyHomePage扩展了StatefulWidget
{

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

类_MyHomePageState扩展State< MyHomePage>
{
@override
小部件构建(BuildContext上下文)
{
A a = Provider.of< A>(上下文);
B b = a.b;
C c = b.c;
D d = c.d;

return脚手架(
正文:Center(
子项:Column(
子项:< Widget> [
Text(
'Current selected Color',
),
占位符(color:d.color,),
],
),
),
floatActionButton:FloatingActionButton(
onPressed:()=> ButtonPressed(上下文),
工具提示:'Increment',
child:Icon(Icons.arrow_forward),
),
) ;
}

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

d.ChangeColor();
}
}

以上显示 Placeholder Widget 的颜色属性由 D类的颜色属性(A-> B-> C-> D.color)定义。 。上面的代码经过了极其简化,但确实显示了我遇到的问题。



回到重点:我将如何分配 child D 的color属性添加到小部件,以便在更新 child D 的属性时,它也会自动更新小部件(使用 notifyListeners() ,而不是 setState())。



我使用了无状态有状态 Provider.of Consumer ,所有这些都给了我相同的结果。只是重申一下,对象不能分离,它必须具有父子关系。






编辑



更复杂的示例:

  import' dart:ui'; 

枚举制造商
{
空客,波音,巴西航空工业公司;
}

类机队
{
List< Aircraft>飞行器;
}

类飞机
{
制造商AircraftManufacturer;
double EmptyWeight;
double长度;
List< Seat>座位
Map< int,CrewMember>机组人员;
}

class CrewMember
{
字符串名称;
字符串姓;
}

班级座位
{
int Row;
颜色SeatColor;
}

上面的代码是真实示例的简化版本。可以想象,兔子洞会越来越深。因此,我通过 A D 的例子的意思是试图简化情况的卷积。



例如,您要在小部件中显示和/或更改机组人员的姓名。在应用程序本身中,您通常会从 Fleet (由传递到小部件)中选择一个飞机列表索引),然后从飞机中选择一个 CrewMember (并通过地图键),然后显示/更改 CrewMember 名称。 / p>

最后,通过使用传入的 Aircrafts 索引和 CrewMembers 键。



我绝对愿意接受更好的体系结构和设计。

解决方案

编辑:对更新后的问题的回答,下面是原始的



不清楚 A B C D 代表您的原始问题。原来是模型



我目前的想法是,用 MultiProvider / ProxyProvider 来提供服务,而不是模型。



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



下面的示例完全可用。为了简单起见,并且由于您要求更新 name 作为示例,所以我仅使该属性设置器为 notifyListeners()

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

main(){
runApp(
MultiProvider(
provider:[Provider.value(value:Service())),
child:MyApp ()

);
}

类MyApp扩展了StatelessWidget {
@override
Widget build(context){
return MaterialApp(
home:Scaffold(
正文:中心(
子项:Consumer< Service>(
构建器:(上下文,服务,_){
return FutureBuilder< Fleet>(
future:服务。 getFleet(),//可能想记住这个未来的
构建器:(上下文,快照){
if(snapshot.hasData){
最终成员= snapshot.data.aircrafts [0] .crewMembers [1];
返回ShowCrewWidget(member);
} else {
return CircularProgressIndicator();
}
}
);
}
),
),
),
);
}
}

类ShowCrewWidget扩展了StatelessWidget {

ShowCrewWidget(this._member);

最终的CrewMember成员;

@override
小部件构建(BuildContext上下文){
return ChangeNotifierProvider< CrewMember>(
create:(_)=> _member,
child :Consumer< CrewMember>(
builder:(_,model,__){
return GestureDetector(
onDoubleTap:()=> model.name ='Peter',
子级:Text(model.name)
);
},
),
);
}
}

枚举制造商{
空客,波音,巴西航空工业公司
}

类Fleet扩展ChangeNotifier {
List< Aircraft>飞机= [];
}

类飞机扩展ChangeNotifier {
制造商aircraftManufacturer;
double emptyWeight;
两倍的长度;
List< Seat>座位
Map< int,CrewMember>机组人员;
}

类CrewMember扩展ChangeNotifier {
CrewMember(this._name);

字符串_name;
字符串姓;

字符串获取名称=> _名称;
set name(String value){
_name = value;
notifyListeners();
}

}

类座位扩展ChangeNotifier {
int row;
颜色seatColor;
}

类服务{

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。



您会注意到,我正在使用服务的顶级注册( Provider.value(值:Service()))和模型的本地级别注册( ChangeNotifierProvider< CrewMember>(创建:...))。



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



关于本地级别的提供程序,我觉得它有点冗长,但是可能有一些方法可以使它更简短。另外,为模型提供一些代码生成库,这些模型可以使用setter通知更改。



(您是否有C#背景?我将您的类固定为与Dart语法。)



让我知道这是否对您有用。






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



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



这有效:

  main( ){
runApp(MultiProvider(
provider:[
ChangeNotifierProvider< D>(创建:(_)=> D()),
ChangeNotifierProxyProvider< D,C>(
创建:(_)=> C(),
更新:(_,d,c)=> c..d = d
),
ChangeNotifierProxyProvider< C,B>(
创建:(_)=> B(),
更新:(_,c,b)=> b..c = c
),
ChangeNotifierProxyProvider< B,A>(
创建:(_)=> A(),
更新:(_,b,a)=> a..b = b
),
],
子级:MyApp(),
));
}

类MyApp扩展了StatelessWidget {
@override
Widget build(context){
return MaterialApp(
title:'我的Flutter App',
家庭:脚手架(
主体:Center(
子对象:Column(
mainAxisAlignment:MainAxisAlignment.center,
子对象:< Widget> [
文本(
'当前选定的颜色',
),
消费者< D>(
构建器:(context,d,_)=>占位符(color:d .color)
),
],
),
),
floatActionButton:FloatingActionButton(
onPressed:()=> Provider.of< D>(上下文,听:false).color = Colors.black,
工具提示:'Increment',
child:Icon(Icons.arrow_forward),
),
),
);
}
}

此应用基于您的 A B C D 类。



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

  Consumer< A>( 
构建器:(context,a,_)=> Text(abcdruntimeType.toString())
),

它将打印出 D。



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



不需要在顶部使用有状态的小部件


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

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

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.

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()).

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.


EDIT

More complex example:

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

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.

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.

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.

解决方案

EDIT: answer to the updated question, original below

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

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

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.

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

}

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".

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.

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.

(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.


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

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

This works:

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

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

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())
),

It will print out "D".

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

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

这篇关于Flutter提供程序嵌套对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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