Line data Source code
1 : import 'package:flutter/widgets.dart';
2 : import 'package:property_change_notifier/property_change_notifier.dart';
3 :
4 : /// An [InheritedWidget] that provides access to a [PropertyChangeNotifier] to descendant widgets.
5 : /// The type parameter [T] is the type of the [PropertyChangeNotifier] subclass.
6 : ///
7 : /// A descendant widget can access the model instance by using the following syntax.
8 : /// This will automatically register the widget to be rebuilt whenever any property changes on the model:
9 : /// ```dart
10 : /// final model = PropertyChangeProvider.of<MyModel>(context).value;
11 : /// ```
12 : ///
13 : /// To access the property name that was changed, use the following syntax:
14 : /// ```dart
15 : /// final property = PropertyChangeProvider.of<MyModel>(context).property;
16 : /// ```
17 : ///
18 : /// To register the widget to be rebuilt only on specific property changes, provide a [properties] parameter:
19 : /// ```dart
20 : /// final model = PropertyChangeProvider.of<MyModel>(context, properties: ['foo', 'bar']).value;
21 : /// ```
22 : ///
23 : /// To access the model without registering the widget to be rebuilt, provide a [listen] parameter with a value of false:
24 : /// ```dart
25 : /// final model = PropertyChangeProvider.of<MyModel>(context, listen: false).value;
26 : /// ```
27 : class PropertyChangeProvider<T extends PropertyChangeNotifier> extends StatefulWidget {
28 : /// Retrieves the [PropertyChangeModel] from the nearest ancestor [PropertyChangeProvider].
29 : /// If [listen] is true (which is the default), the calling widget will also be rebuilt
30 : /// whenever the ancestor's [PropertyChangeNotifier] model changes. To only rebuild
31 : /// for certain properties, provide them in the [properties] parameter.
32 : /// If [listen] is false, the [properties] parameter must be null or empty.
33 2 : static PropertyChangeModel<T> of<T extends PropertyChangeNotifier>(
34 : BuildContext context, {
35 : Iterable<Object> properties,
36 : bool listen = true,
37 : }) {
38 1 : assert(listen || properties == null, "Don't provide properties if you're not going to listen to them.");
39 :
40 1 : final typeOf = <T>() => T;
41 :
42 2 : final nullCheck = (InheritedModel model) {
43 4 : assert(model != null, 'Could not find an ancestor PropertyChangeProvider<$T>');
44 : return model;
45 : };
46 :
47 : if (!listen) {
48 1 : final type = typeOf<PropertyChangeModel<T>>();
49 2 : return nullCheck(context.ancestorWidgetOfExactType(type) as PropertyChangeModel);
50 : }
51 :
52 2 : if (properties == null || properties.isEmpty) {
53 4 : return nullCheck(InheritedModel.inheritFrom<PropertyChangeModel<T>>(context));
54 : }
55 :
56 : InheritedModel widget;
57 4 : for (final property in properties) {
58 2 : widget = InheritedModel.inheritFrom<PropertyChangeModel<T>>(context, aspect: property);
59 : }
60 :
61 2 : return nullCheck(widget);
62 : }
63 :
64 : /// Creates a [PropertyChangeProvider] that can be accessed by descendant widgets.
65 2 : PropertyChangeProvider({
66 : Key key,
67 : @required this.value,
68 : @required this.child,
69 1 : }) : assert(value != null),
70 1 : assert(child != null),
71 2 : super(key: key);
72 :
73 : /// The instance of [T] to provide to descendant widgets.
74 : final T value;
75 :
76 : /// The widget below this widget in the tree.
77 : ///
78 : /// {@macro flutter.widgets.child}
79 : final Widget child;
80 :
81 2 : @override
82 2 : _PropertyChangeProviderState createState() => _PropertyChangeProviderState<T>();
83 : }
84 :
85 : /// The companion [State] object to [PropertyChangeProvider]. For private use only.
86 : /// Subscribes as a global listener to the [PropertyChangeNotifier] instance at [widget].[value].
87 : /// Rebuilds whenever a property is changed and creates a new [PropertyChangeModel] with a reference
88 : /// to itself so it can access the original model instance and newly changed property name.
89 : class _PropertyChangeProviderState<T extends PropertyChangeNotifier> extends State<PropertyChangeProvider<T>> {
90 : Object _property;
91 :
92 2 : @override
93 : void initState() {
94 2 : super.initState();
95 8 : widget.value.addListener(_listener);
96 : }
97 :
98 2 : @override
99 : void dispose() {
100 8 : widget.value.removeListener(_listener);
101 2 : super.dispose();
102 : }
103 :
104 2 : @override
105 : Widget build(BuildContext context) {
106 2 : return PropertyChangeModel<T>(
107 : state: this,
108 4 : child: widget.child,
109 : );
110 : }
111 :
112 2 : void _listener(Object property) {
113 4 : setState(() {
114 2 : _property = property;
115 : });
116 : }
117 : }
118 :
119 : /// The [InheritedModel] subclass that is rebuilt by [_PropertyChangeProviderState]
120 : /// whenever its [PropertyChangeNotifier] is updated. Notifies dependents when the
121 : /// name of the changed property is contained in the list of properties provided
122 : /// when the widgets originally called the [PropertyChangeProvider].[of] method.
123 : /// The type parameter [T] is the type of the [PropertyChangeNotifier] subclass.
124 : class PropertyChangeModel<T extends PropertyChangeNotifier> extends InheritedModel {
125 : final _PropertyChangeProviderState _state;
126 :
127 2 : PropertyChangeModel({
128 : Key key,
129 : _PropertyChangeProviderState state,
130 : Widget child,
131 : }) : _state = state,
132 2 : super(key: key, child: child);
133 :
134 : /// The instance of [T] originally provided to the [PropertyChangeProvider] constructor.
135 8 : T get value => _state.widget.value;
136 :
137 : /// The name of the property that was last changed on the [value] instance.
138 6 : Object get property => _state._property;
139 :
140 2 : @override
141 : bool updateShouldNotify(PropertyChangeModel oldWidget) {
142 : return true;
143 : }
144 :
145 2 : @override
146 : bool updateShouldNotifyDependent(PropertyChangeModel<T> oldWidget, Set<Object> aspects) {
147 6 : return aspects.contains(_state._property);
148 : }
149 : }
150 :
151 : /// A widget-based listener for cases where a [BuildContext] is hard to access, or if you prefer this kind of API.
152 : /// To register the widget to be rebuilt only on specific property changes, provide a [properties] parameter.
153 : ///
154 : /// Access both the model value and the changed property via the [builder] callback:
155 : /// ```dart
156 : /// PropertyChangeConsumer<MyModel>(
157 : /// properties: ['foo', 'bar'],
158 : /// builder: (context, model, property) {
159 : /// return Column(
160 : /// children: [
161 : /// Text('$property was changed!'),
162 : /// RaisedButton(
163 : /// child: Text('Update foo'),
164 : /// onPressed: () {
165 : /// model.foo = DateTime.now().toString();
166 : /// },
167 : /// ),
168 : /// RaisedButton(
169 : /// child: Text('Update bar'),
170 : /// onPressed: () {
171 : /// model.bar = DateTime.now().toString();
172 : /// },
173 : /// ),
174 : /// ],
175 : /// );
176 : /// },
177 : /// );
178 : /// ```
179 : class PropertyChangeConsumer<T extends PropertyChangeNotifier> extends StatelessWidget {
180 : final Iterable<Object> properties;
181 : final Widget Function(BuildContext, T, Object) builder;
182 :
183 1 : PropertyChangeConsumer({
184 : Key key,
185 : this.properties,
186 : @required this.builder,
187 1 : }) : assert(builder != null),
188 1 : super(key: key);
189 :
190 1 : @override
191 : Widget build(BuildContext context) {
192 2 : final model = PropertyChangeProvider.of<T>(context, properties: this.properties, listen: true);
193 3 : return this.builder(context, model.value, model.property);
194 : }
195 : }
|