1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'app.dart'; |
6 | library; |
7 | |
8 | import 'framework.dart'; |
9 | import 'inherited_model.dart'; |
10 | |
11 | /// The type of the [SharedAppData.getValue] `init` parameter. |
12 | /// |
13 | /// This callback is used to lazily create the initial value for |
14 | /// a [SharedAppData] keyword. |
15 | typedef SharedAppDataInitCallback<T> = T Function(); |
16 | |
17 | /// Enables sharing key/value data with its `child` and all of the |
18 | /// child's descendants. |
19 | /// |
20 | /// - `SharedAppData.getValue(context, key, initCallback)` creates a dependency |
21 | /// on the key and returns the value for the key from the shared data table. |
22 | /// If no value exists for key then the initCallback is used to create |
23 | /// the initial value. |
24 | /// |
25 | /// - `SharedAppData.setValue(context, key, value)` changes the value of an entry |
26 | /// in the shared data table and forces widgets that depend on that entry |
27 | /// to be rebuilt. |
28 | /// |
29 | /// A widget whose build method uses SharedAppData.getValue(context, |
30 | /// keyword, initCallback) creates a dependency on the SharedAppData. When |
31 | /// the value of keyword changes with SharedAppData.setValue(), the widget |
32 | /// will be rebuilt. The values managed by the SharedAppData are expected |
33 | /// to be immutable: intrinsic changes to values will not cause |
34 | /// dependent widgets to be rebuilt. |
35 | /// |
36 | /// An instance of this widget is created automatically by [WidgetsApp]. |
37 | /// |
38 | /// There are many ways to share data with a widget subtree. This |
39 | /// class is based on [InheritedModel], which is an [InheritedWidget]. |
40 | /// It's intended to be used by packages that need to share a modest |
41 | /// number of values among their own components. |
42 | /// |
43 | /// SharedAppData is not intended to be a substitute for Provider or any of |
44 | /// the other general purpose application state systems. SharedAppData is |
45 | /// for situations where a package's custom widgets need to share one |
46 | /// or a handful of immutable data objects that can be lazily |
47 | /// initialized. It exists so that packages like that can deliver |
48 | /// custom widgets without requiring the developer to add a |
49 | /// package-specific umbrella widget to their application. |
50 | /// |
51 | /// A good way to create an SharedAppData key that avoids potential |
52 | /// collisions with other packages is to use a static `Object()` value. |
53 | /// The `SharedObject` example below does this. |
54 | /// |
55 | /// {@tool dartpad} |
56 | /// The following sample demonstrates using the automatically created |
57 | /// [SharedAppData]. Button presses cause changes to the values for keys |
58 | /// 'foo', and 'bar', and those changes only cause the widgets that |
59 | /// depend on those keys to be rebuilt. |
60 | /// |
61 | /// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart ** |
62 | /// {@end-tool} |
63 | /// |
64 | /// {@tool dartpad} |
65 | /// The following sample demonstrates how a single lazily computed |
66 | /// value could be shared within an app. A Flutter package that |
67 | /// provided custom widgets might use this approach to share a (possibly |
68 | /// private) value with instances of those widgets. |
69 | /// |
70 | /// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart ** |
71 | /// {@end-tool} |
72 | class SharedAppData extends StatefulWidget { |
73 | /// Creates a widget based on [InheritedModel] that supports build |
74 | /// dependencies qualified by keywords. Descendant widgets create |
75 | /// such dependencies with [SharedAppData.getValue] and they trigger |
76 | /// rebuilds with [SharedAppData.setValue]. |
77 | /// |
78 | /// This widget is automatically created by the [WidgetsApp]. |
79 | const SharedAppData({super.key, required this.child}); |
80 | |
81 | /// The widget below this widget in the tree. |
82 | /// |
83 | /// {@macro flutter.widgets.ProxyWidget.child} |
84 | final Widget child; |
85 | |
86 | @override |
87 | State<StatefulWidget> createState() => _SharedAppDataState(); |
88 | |
89 | /// Returns the app model's value for `key` and ensures that each |
90 | /// time the value of `key` is changed with [SharedAppData.setValue], the |
91 | /// specified context will be rebuilt. |
92 | /// |
93 | /// If no value for `key` exists then the `init` callback is used to |
94 | /// generate an initial value. The callback is expected to return |
95 | /// an immutable value because intrinsic changes to the value will |
96 | /// not cause dependent widgets to be rebuilt. |
97 | /// |
98 | /// A widget that depends on the app model's value for `key` should use |
99 | /// this method in their `build` methods to ensure that they are rebuilt |
100 | /// if the value changes. |
101 | /// |
102 | /// The type parameter `K` is the type of the keyword and `V` |
103 | /// is the type of the value. |
104 | static V getValue<K extends Object, V>( |
105 | BuildContext context, |
106 | K key, |
107 | SharedAppDataInitCallback<V> init, |
108 | ) { |
109 | final _SharedAppModel? model = InheritedModel.inheritFrom<_SharedAppModel>( |
110 | context, |
111 | aspect: key, |
112 | ); |
113 | assert(_debugHasSharedAppData(model, context, 'getValue' )); |
114 | return model!.sharedAppDataState.getValue<K, V>(key, init); |
115 | } |
116 | |
117 | /// Changes the app model's `value` for `key` and rebuilds any widgets |
118 | /// that have created a dependency on `key` with [SharedAppData.getValue]. |
119 | /// |
120 | /// If `value` is `==` to the current value of `key` then nothing |
121 | /// is rebuilt. |
122 | /// |
123 | /// The `value` is expected to be immutable because intrinsic |
124 | /// changes to the value will not cause dependent widgets to be |
125 | /// rebuilt. |
126 | /// |
127 | /// Unlike [SharedAppData.getValue], this method does _not_ create a dependency |
128 | /// between `context` and `key`. |
129 | /// |
130 | /// The type parameter `K` is the type of the value's keyword and `V` |
131 | /// is the type of the value. |
132 | static void setValue<K extends Object, V>(BuildContext context, K key, V value) { |
133 | final _SharedAppModel? model = context.getInheritedWidgetOfExactType<_SharedAppModel>(); |
134 | assert(_debugHasSharedAppData(model, context, 'setValue' )); |
135 | model!.sharedAppDataState.setValue<K, V>(key, value); |
136 | } |
137 | |
138 | static bool _debugHasSharedAppData( |
139 | _SharedAppModel? model, |
140 | BuildContext context, |
141 | String methodName, |
142 | ) { |
143 | assert(() { |
144 | if (model == null) { |
145 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
146 | ErrorSummary('No SharedAppData widget found.' ), |
147 | ErrorDescription( |
148 | 'SharedAppData. $methodName requires an SharedAppData widget ancestor.\n' , |
149 | ), |
150 | context.describeWidget( |
151 | 'The specific widget that could not find an SharedAppData ancestor was' , |
152 | ), |
153 | context.describeOwnershipChain('The ownership chain for the affected widget is' ), |
154 | ErrorHint( |
155 | 'Typically, the SharedAppData widget is introduced by the MaterialApp ' |
156 | 'or WidgetsApp widget at the top of your application widget tree. It ' |
157 | 'provides a key/value map of data that is shared with the entire ' |
158 | 'application.' , |
159 | ), |
160 | ]); |
161 | } |
162 | return true; |
163 | }()); |
164 | return true; |
165 | } |
166 | } |
167 | |
168 | class _SharedAppDataState extends State<SharedAppData> { |
169 | late Map<Object, Object?> data = <Object, Object?>{}; |
170 | |
171 | @override |
172 | Widget build(BuildContext context) { |
173 | return _SharedAppModel(sharedAppDataState: this, child: widget.child); |
174 | } |
175 | |
176 | V getValue<K extends Object, V>(K key, SharedAppDataInitCallback<V> init) { |
177 | data[key] ??= init(); |
178 | return data[key] as V; |
179 | } |
180 | |
181 | void setValue<K extends Object, V>(K key, V value) { |
182 | if (data[key] != value) { |
183 | setState(() { |
184 | data = Map<Object, Object?>.of(data); |
185 | data[key] = value; |
186 | }); |
187 | } |
188 | } |
189 | } |
190 | |
191 | class _SharedAppModel extends InheritedModel<Object> { |
192 | _SharedAppModel({required this.sharedAppDataState, required super.child}) |
193 | : data = sharedAppDataState.data; |
194 | |
195 | final _SharedAppDataState sharedAppDataState; |
196 | final Map<Object, Object?> data; |
197 | |
198 | @override |
199 | bool updateShouldNotify(_SharedAppModel old) { |
200 | return data != old.data; |
201 | } |
202 | |
203 | @override |
204 | bool updateShouldNotifyDependent(_SharedAppModel old, Set<Object> keys) { |
205 | for (final Object key in keys) { |
206 | if (data[key] != old.data[key]) { |
207 | return true; |
208 | } |
209 | } |
210 | return false; |
211 | } |
212 | } |
213 | |