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