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
6import 'framework.dart';
7import '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.
13typedef 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}
70class 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
153class _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
176class _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