Saltar al contenido

Objetos anidados del proveedor de Flutter

Solución:

EDITAR: respuesta a la pregunta actualizada, original a continuación

No estaba claro que A, B, C y D representaba en su pregunta original. Resulta que esos eran modelos.

Mi pensamiento actual es, envuelva su aplicación con MultiProvider/ProxyProvider Para proveer servicios, no modelos.

No estoy seguro de cómo está cargando sus datos (si es que lo hace), pero asumí un servicio que recupera su flota de forma asincrónica. Si sus datos son cargados por partes / modelos a través de diferentes servicios (en lugar de todos a la vez), puede agregarlos al MultiProvider e inyectarlos en los widgets apropiados cuando necesite cargar más datos.

El siguiente ejemplo es completamente funcional. En aras de la simplicidad, y ya que preguntaste sobre la actualización name como ejemplo, solo hice ese establecedor de propiedades 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(
            builder: (context, service, _) 
              return FutureBuilder(
                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(
      create: (_) => _member,
      child: Consumer(
        builder: (_, model, __) 
          return GestureDetector(
            onDoubleTap: () => model.name = 'Peter',
            child: Text(model.name)
          );
        ,
      ),
    );
  


enum Manufacturer 
    Airbus, Boeing, Embraer


class Fleet extends ChangeNotifier 
    List aircrafts = [];


class Aircraft extends ChangeNotifier 
    Manufacturer        aircraftManufacturer;
    double              emptyWeight;
    double              length;
    List          seats;
    Map 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 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);
  


Ejecute la aplicación, espere 2 segundos para que se carguen los datos y debería ver “John”, que es un miembro de la tripulación con id = 1 en ese mapa. Luego, toca dos veces el texto y debería actualizarse a “Peter”.

Como puede notar, estoy usando el registro de servicios de nivel superior (Provider.value(value: Service())) y el registro de modelos a nivel local (ChangeNotifierProvider(create: ...)).

Creo que esta arquitectura (con una cantidad razonable de modelos) debería ser factible.

Con respecto al proveedor de nivel local, lo encuentro un poco detallado, pero puede haber formas de acortarlo. Además, sería increíble tener alguna biblioteca de generación de código para modelos con establecedores para notificar cambios.

(¿Tiene experiencia en C #? Arreglé sus clases para que estuvieran en línea con la sintaxis de Dart).

Hazme saber si esto funciona para ti.


Si desea utilizar Provider, deberá crear el gráfico de dependencia con Provider.

(Puede elegir inyección de constructor, en lugar de inyección de setter)

Esto funciona:

main() 
  runApp(MultiProvider(
    providers: [
        ChangeNotifierProvider(create: (_) => D()),
        ChangeNotifierProxyProvider(
          create: (_) => C(),
          update: (_, d, c) => c..d=d
        ),
        ChangeNotifierProxyProvider(
          create: (_) => B(),
          update: (_, c, b) => b..c=c
        ),
        ChangeNotifierProxyProvider(
          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: [
                      Text(
                          'Current selected Color',
                      ),
                      Consumer(
                        builder: (context, d, _) => Placeholder(color: d.color)
                      ),
                  ],
              ),
          ),
          floatingActionButton: FloatingActionButton(
              onPressed: () => Provider.of(context, listen: false).color = Colors.black,
              tooltip: 'Increment',
              child: Icon(Icons.arrow_forward),
          ),
      ),
    );
  

Esta aplicación funciona en función de su A, B, C y D clases.

Su ejemplo no usa proxies ya que solo usa D que no tiene dependencias. Pero puede ver que el proveedor ha conectado las dependencias correctamente con este ejemplo:

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

Se imprimirá “D”.

ChangeColor() no funcionó porque no está llamando notifyListeners().

No es necesario utilizar un widget con estado además de esto.

Como he expresado antes, la configuración que tiene parece demasiado complicada. Cada instancia de una clase modelo es una ChangeNotifier y por tanto es responsable de mantenerse. Este es un problema arquitectónico que dará lugar a problemas de escalado y mantenimiento en el futuro.

Casi todas las arquitecturas de software existentes tienen algo en común: separar el estado del controlador. Los datos solo deben ser datos. No debería tener que preocuparse por las operaciones del resto del programa. Mientras tanto, el controlador (el bloque, el modelo de vista, el administrador, el servicio o como quieras llamarlo) proporciona la interfaz para que el resto del programa acceda o modifique los datos. De esta manera, mantenemos una separación de preocupaciones y reducimos el número de puntos de interacción entre servicios, reduciendo así en gran medida las relaciones de dependencia (lo que contribuye en gran medida a mantener el programa simple y fácil de mantener).

En este caso, un buen ajuste podría ser el enfoque de estado inmutable. En este enfoque, las clases modelo son solo eso: inmutables. Si desea cambiar algo en un modelo, en lugar de actualizar un campo, intercambia toda la instancia de la clase del modelo. Esto puede parecer un desperdicio, pero en realidad crea varias propiedades en la administración de su estado por diseño:

  1. Sin la capacidad de cambiar los campos directamente, los consumidores del modelo se ven obligados a utilizar los puntos finales de actualización en el controlador.
  2. Cada clase de modelo se convierte en una fuente de verdad autónoma que no afectará ninguna cantidad de refactorización en el resto de su programa, eliminando los efectos secundarios del acoplamiento excesivo.
  3. Cada instancia representa un estado completamente nuevo para que exista su programa, por lo que con el mecanismo de escucha adecuado (logrado aquí con el proveedor) es extremadamente simple decirle al programa que se actualice en función de un cambio de estado.

A continuación, se muestra un ejemplo de cómo las clases de su modelo podrían estar representadas por la administración de estado inmutable:

main() 
  runApp(
    ChangeNotifierProvider(
      create: FleetManager(),
      child: MyApp(),
    ),
  );


...

class FleetManager extends ChangeNotifier 
  final _fleet = ;
  Map get fleet => Map.unmodifiable(_fleet);

  void updateAircraft(String id, Aircraft aircraft) 
    _fleet[id] = aircraft;
    notifyListeners();
  

  void removeAircraft(String id) 
    _fleet.remove(id);
    notifyListeners();
  


class Aircraft 
  Aircraft(
    this.aircraftManufacturer,
    this.emptyWeight,
    this.length,
    this.seats = const ,
    this.crewMembers = const ,
  );

  final String aircraftManufacturer;
  final double emptyWeight;
  final double length;
  final Map seats;
  final Map crewMembers;

  Aircraft copyWith(
    String aircraftManufacturer,
    double emptyWeight,
    double length,
    Map seats,
    Map crewMembers,
  ) => Aircraft(
    aircraftManufacturer: aircraftManufacturer ?? this.aircraftManufacturer,
    emptyWeight: emptyWeight ?? this.emptyWeight,
    length: length ?? this.length,
    seats: seats ?? this.seats,
    crewMembers: crewMembers ?? this.crewMembers,
  );

  Aircraft withSeat(int id, Seat seat) 
    return Aircraft.copyWith(seats: 
      ...this.seats,
      id: seat,
    );
  

  Aircraft withCrewMember(int id, CrewMember crewMember) 
    return Aircraft.copyWith(seats: 
      ...this.crewMembers,
      id: crewMember,
    );
  


class CrewMember 
  CrewMember(
    this.firstName,
    this.lastName,
  );

  final String firstName;
  final String lastName;

  CrewMember copyWith(
    String firstName,
    String lastName,
  ) => CrewMember(
    firstName: firstName ?? this.firstName,
    lastName: lastName ?? this.lastName,
  );


class Seat 
  Seat(
    this.row,
    this.seatColor,
  );

  final int row;
  final Color seatColor;

  Seat copyWith(
    String row,
    String seatColor,
  ) => Seat(
    row: row ?? this.row,
    seatColor: seatColor ?? this.seatColor,
  );

Siempre que desee agregar, modificar o eliminar una aeronave de la flota, debe pasar por FleetManager, no los modelos individuales. Por ejemplo, si tuviera un miembro de la tripulación y quisiera cambiar su nombre, lo haría así:

final oldCrewMember = oldAircraft.crewMembers[selectedCrewMemberId];
final newCrewMember = oldCrewMember.copyWith(firstName: 'Jane');
final newAircraft = oldAircraft.withCrewMember(selectedCrewMemberId, newCrewMember);
fleetManager.updateAircraft(aircraftId, newAircraft);

Claro, es un poco más detallado que solo crewMember.firstName = 'Jane';, pero considere los beneficios arquitectónicos en juego aquí. Con este enfoque, no tenemos una red masiva de interdependencias, donde un cambio en cualquier lugar podría tener repercusiones en muchos otros lugares, algunos de los cuales pueden ser involuntarios. Solo hay un estado, por lo que solo hay un lugar donde algo podría cambiar. Cualquier otra cosa que escuche este cambio tiene que pasar FleetManager, por lo que solo hay un punto de interfaz del que preocuparse: un punto de falla en lugar de potencialmente docenas. Con toda esta seguridad y simplicidad arquitectónica, un poco más de verbosidad en el código es un intercambio que vale la pena.

Este es un ejemplo un poco simple, y aunque definitivamente hay formas de mejorarlo, hay paquetes para manejar este tipo de cosas por nosotros de todos modos. Para ejecuciones más sólidas de administración de estado inmutable, recomendaría revisar los paquetes flutter_bloc o redux. El paquete redux es esencialmente un puerto directo de Redux en React to Flutter, por lo que si tiene experiencia React, se sentirá como en casa. El paquete flutter_bloc adopta un enfoque ligeramente menos reglamentado para el estado inmutable y también incorpora el patrón de máquina de estado finito, que reduce aún más las complejidades que rodean a cómo saber en qué estado se encuentra su aplicación en un momento dado.

(Tenga en cuenta también que en este ejemplo, cambié el Manufacturer enumeración para ser solo un campo de cadena en el Airline clase. Esto se debe a que hay tantos fabricantes de aerolíneas en el mundo que será una tarea difícil mantenerse al día con todos ellos, y cualquier fabricante que no esté representado por la enumeración no se puede almacenar en el modelo de flota. Hacer que sea una cuerda es solo una cosa menos que necesita mantener activamente).

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *