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<S>] subclass. 6 : /// The type parameter [S] is the type of the properties to observe. 7 : /// 8 : /// Given the following model: 9 : /// ```dart 10 : /// class MyModel extends PropertyChangeNotifier<String> {...} 11 : /// ``` 12 : /// 13 : /// A descendant widget can access the model instance by using the following syntax. 14 : /// This will automatically register the widget to be rebuilt whenever any property changes on the model: 15 : /// ```dart 16 : /// final model = PropertyChangeProvider.of<MyModel, String>(context).value; 17 : /// ``` 18 : /// 19 : /// To access the properties that were changed in the current build frame, use the following syntax. 20 : /// ```dart 21 : /// final properties = PropertyChangeProvider.of<MyModel, String>(context).properties; 22 : /// ``` 23 : /// 24 : /// To register the widget to be rebuilt only on specific property changes, provide a [properties] parameter: 25 : /// ```dart 26 : /// final model = PropertyChangeProvider.of<MyModel, String>(context, properties: ['foo', 'bar']).value; 27 : /// ``` 28 : /// 29 : /// To only access the model without registering the widget to be rebuilt, provide a [listen] parameter with a value of false: 30 : /// ```dart 31 : /// final model = PropertyChangeProvider.of<MyModel, String>(context, listen: false).value; 32 : /// ``` 33 : class PropertyChangeProvider<T extends PropertyChangeNotifier<S>, S extends Object> extends StatefulWidget { 34 : /// Retrieves the [PropertyChangeModel] from the nearest ancestor [PropertyChangeProvider]. 35 : /// If [listen] is true (which is the default), the calling widget will also be rebuilt 36 : /// whenever the ancestor's [PropertyChangeNotifier] model changes. To only rebuild 37 : /// for certain properties, provide them in the [properties] parameter. 38 : /// If [listen] is false, the [properties] parameter must be null or empty. 39 2 : static PropertyChangeModel<T, S>? of<T extends PropertyChangeNotifier<S>, S extends Object>( 40 : BuildContext context, { 41 : Iterable<S>? properties, 42 : bool listen = true, 43 : }) { 44 1 : assert(listen || properties == null, "Don't provide properties if you're not going to listen to them."); 45 : 46 2 : PropertyChangeModel<T, S>? nullCheck(PropertyChangeModel<T, S>? model) { 47 4 : assert(model != null, 'Could not find an ancestor PropertyChangeProvider<$T, $S>'); 48 : return model; 49 : }; 50 : 51 : if (!listen) { 52 2 : return nullCheck(context.findAncestorWidgetOfExactType<PropertyChangeModel<T, S>>()); 53 : } 54 : 55 2 : if (properties == null || properties.isEmpty) { 56 4 : return nullCheck(context.dependOnInheritedWidgetOfExactType<PropertyChangeModel<T, S>>()); 57 : } 58 : 59 : PropertyChangeModel<T, S>? widget; 60 4 : for (final property in properties) { 61 2 : widget = InheritedModel.inheritFrom<PropertyChangeModel<T, S>>(context, aspect: property); 62 : } 63 : 64 2 : return nullCheck(widget); 65 : } 66 : 67 : /// Creates a [PropertyChangeProvider] that can be accessed by descendant widgets. 68 2 : const PropertyChangeProvider({ 69 : Key? key, 70 : required this.value, 71 : required this.child, 72 2 : }) : super(key: key); 73 : 74 : /// The instance of [T] to provide to descendant widgets. 75 : final T value; 76 : 77 : /// The widget below this widget in the tree. 78 : /// 79 : /// {@macro flutter.widgets.child} 80 : final Widget child; 81 : 82 2 : @override 83 2 : _PropertyChangeProviderState createState() => _PropertyChangeProviderState<T, S>(); 84 : } 85 : 86 : /// A convenience typedef to use in the common use case where property names are of type [String]. 87 : typedef StringPropertyChangeProvider<T extends PropertyChangeNotifier<String>> = PropertyChangeProvider<T, String>; 88 : 89 : /// The companion [State] object to [PropertyChangeProvider]. For private use only. 90 : /// Subscribes as a global listener to the [PropertyChangeNotifier] instance at [widget].[value]. 91 : /// Rebuilds whenever a property is changed and creates a new [PropertyChangeModel] with a reference 92 : /// to itself so it can access the original model instance and changed property names. 93 : class _PropertyChangeProviderState<T extends PropertyChangeNotifier<S>, S extends Object> extends State<PropertyChangeProvider<T, S>> { 94 : Set<S> _properties = {}; 95 : 96 2 : @override 97 : void initState() { 98 2 : super.initState(); 99 8 : widget.value.addListener(_listener); 100 : } 101 : 102 2 : @override 103 : void dispose() { 104 8 : widget.value.removeListener(_listener); 105 2 : super.dispose(); 106 : } 107 : 108 2 : @override 109 : Widget build(BuildContext context) { 110 2 : return PropertyChangeModel<T, S>( 111 : state: this, 112 4 : child: widget.child, 113 : ); 114 : } 115 : 116 2 : void _listener(S? property) { 117 4 : setState(() { 118 2 : _addProperty(property); 119 : }); 120 : } 121 : 122 2 : void _addProperty(S? property) { 123 : if (property == null) return; 124 2 : final element = context as StatefulElement; 125 2 : if (element.dirty) { 126 2 : _properties.add(property); 127 : } else { 128 4 : _properties = {property}; 129 : } 130 : } 131 : } 132 : 133 : /// The [InheritedModel] subclass that is rebuilt by [_PropertyChangeProviderState] 134 : /// whenever its [PropertyChangeNotifier] is updated. Notifies dependents when the 135 : /// names of the changed properties intersect with the list of properties provided 136 : /// to the [PropertyChangeProvider].[of] method. 137 : /// The type parameter [T] is the type of the [PropertyChangeNotifier<S>] subclass. 138 : /// The type parameter [S] is the type of the properties to observe. 139 : class PropertyChangeModel<T extends PropertyChangeNotifier<S>, S extends Object> extends InheritedModel<S> { 140 : final _PropertyChangeProviderState<T, S> _state; 141 : 142 2 : const PropertyChangeModel({ 143 : Key? key, 144 : required _PropertyChangeProviderState<T, S> state, 145 : required Widget child, 146 : }) : _state = state, 147 2 : super(key: key, child: child); 148 : 149 : /// The instance of [T] originally provided to the [PropertyChangeProvider] constructor. 150 8 : T get value => _state.widget.value; 151 : 152 : /// The names of the properties on the [value] instance that were changed in the current build frame. 153 6 : Set<S> get properties => _state._properties; 154 : 155 2 : @override 156 : bool updateShouldNotify(PropertyChangeModel oldWidget) { 157 : return true; 158 : } 159 : 160 2 : @override 161 : bool updateShouldNotifyDependent(PropertyChangeModel<T, S> oldWidget, Set<S> aspects) { 162 8 : return aspects.intersection(_state._properties).isNotEmpty; 163 : } 164 : }