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.