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 properties that were changed in the current build frame, use the following syntax. 14 : /// ```dart 15 : /// final properties = PropertyChangeProvider.of<MyModel>(context).properties; 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 changed property names. 89 : class _PropertyChangeProviderState<T extends PropertyChangeNotifier> extends State<PropertyChangeProvider<T>> { 90 : Set<Object> _properties = {}; 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 : _addProperty(property); 115 : }); 116 : } 117 : 118 2 : void _addProperty(Object property) { 119 2 : final element = this.context as StatefulElement; 120 2 : if (element.dirty) { 121 2 : _properties.add(property); 122 : } else { 123 2 : _properties = {property}; 124 : } 125 : } 126 : } 127 : 128 : /// The [InheritedModel] subclass that is rebuilt by [_PropertyChangeProviderState] 129 : /// whenever its [PropertyChangeNotifier] is updated. Notifies dependents when the 130 : /// names of the changed properties intersect with the list of properties provided 131 : /// to the [PropertyChangeProvider].[of] method. 132 : /// The type parameter [T] is the type of the [PropertyChangeNotifier] subclass. 133 : class PropertyChangeModel<T extends PropertyChangeNotifier> extends InheritedModel { 134 : final _PropertyChangeProviderState _state; 135 : 136 2 : PropertyChangeModel({ 137 : Key key, 138 : _PropertyChangeProviderState state, 139 : Widget child, 140 : }) : _state = state, 141 2 : super(key: key, child: child); 142 : 143 : /// The instance of [T] originally provided to the [PropertyChangeProvider] constructor. 144 8 : T get value => _state.widget.value; 145 : 146 : /// The names of the properties on the [value] instance that were changed in the current build frame. 147 6 : Set<Object> get properties => _state._properties; 148 : 149 2 : @override 150 : bool updateShouldNotify(PropertyChangeModel oldWidget) { 151 : return true; 152 : } 153 : 154 2 : @override 155 : bool updateShouldNotifyDependent(PropertyChangeModel<T> oldWidget, Set<Object> aspects) { 156 8 : return aspects.intersection(_state._properties).isNotEmpty; 157 : } 158 : }