| 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 | |