1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'framework.dart';
/// A [ValueKey] that defines where [PageStorage] values will be saved.
///
/// [Scrollable]s ([ScrollPosition]s really) use [PageStorage] to save their
/// scroll offset. Each time a scroll completes, the scrollable's page
/// storage is updated.
///
/// [PageStorage] is used to save and restore values that can outlive the widget.
/// The values are stored in a per-route [Map] whose keys are defined by the
/// [PageStorageKey]s for the widget and its ancestors. To make it possible
/// for a saved value to be found when a widget is recreated, the key's values
/// must not be objects whose identity will change each time the widget is created.
///
/// For example, to ensure that the scroll offsets for the scrollable within
/// each `MyScrollableTabView` below are restored when the [TabBarView]
/// is recreated, we've specified [PageStorageKey]s whose values are the
/// tabs' string labels.
///
/// ```dart
/// TabBarView(
/// children: myTabs.map((Tab tab) {
/// MyScrollableTabView(
/// key: PageStorageKey<String>(tab.text), // like 'Tab 1'
/// tab: tab,
/// ),
/// }),
/// )
/// ```
class PageStorageKey<T> extends ValueKey<T> {
/// Creates a [ValueKey] that defines where [PageStorage] values will be saved.
const PageStorageKey(T value) : super(value);
}
class _StorageEntryIdentifier {
_StorageEntryIdentifier(this.keys)
: assert(keys != null);
final List<PageStorageKey<dynamic>> keys;
bool get isNotEmpty => keys.isNotEmpty;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final _StorageEntryIdentifier typedOther = other;
for (int index = 0; index < keys.length; index += 1) {
if (keys[index] != typedOther.keys[index])
return false;
}
return true;
}
@override
int get hashCode => hashList(keys);
@override
String toString() {
return 'StorageEntryIdentifier(${keys?.join(":")})';
}
}
/// A storage bucket associated with a page in an app.
///
/// Useful for storing per-page state that persists across navigations from one
/// page to another.
class PageStorageBucket {
static bool _maybeAddKey(BuildContext context, List<PageStorageKey<dynamic>> keys) {
final Widget widget = context.widget;
final Key key = widget.key;
if (key is PageStorageKey)
keys.add(key);
return widget is! PageStorage;
}
List<PageStorageKey<dynamic>> _allKeys(BuildContext context) {
final List<PageStorageKey<dynamic>> keys = <PageStorageKey<dynamic>>[];
if (_maybeAddKey(context, keys)) {
context.visitAncestorElements((Element element) {
return _maybeAddKey(element, keys);
});
}
return keys;
}
_StorageEntryIdentifier _computeIdentifier(BuildContext context) {
return _StorageEntryIdentifier(_allKeys(context));
}
Map<Object, dynamic> _storage;
/// Write the given data into this page storage bucket using the
/// specified identifier or an identifier computed from the given context.
/// The computed identifier is based on the [PageStorageKey]s
/// found in the path from context to the [PageStorage] widget that
/// owns this page storage bucket.
///
/// If an explicit identifier is not provided and no [PageStorageKey]s
/// are found, then the `data` is not saved.
void writeState(BuildContext context, dynamic data, { Object identifier }) {
_storage ??= <Object, dynamic>{};
if (identifier != null) {
_storage[identifier] = data;
} else {
final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
if (contextIdentifier.isNotEmpty)
_storage[contextIdentifier] = data;
}
}
/// Read given data from into this page storage bucket using the specified
/// identifier or an identifier computed from the given context.
/// The computed identifier is based on the [PageStorageKey]s
/// found in the path from context to the [PageStorage] widget that
/// owns this page storage bucket.
///
/// If an explicit identifier is not provided and no [PageStorageKey]s
/// are found, then null is returned.
dynamic readState(BuildContext context, { Object identifier }) {
if (_storage == null)
return null;
if (identifier != null)
return _storage[identifier];
final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
return contextIdentifier.isNotEmpty ? _storage[contextIdentifier] : null;
}
}
/// A widget that establishes a page storage bucket for this widget subtree.
class PageStorage extends StatelessWidget {
/// Creates a widget that provides a storage bucket for its descendants.
///
/// The [bucket] argument must not be null.
const PageStorage({
Key key,
@required this.bucket,
@required this.child
}) : assert(bucket != null),
super(key: key);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// The page storage bucket to use for this subtree.
final PageStorageBucket bucket;
/// The bucket from the closest instance of this class that encloses the given context.
///
/// Returns null if none exists.
///
/// Typical usage is as follows:
///
/// ```dart
/// PageStorageBucket bucket = PageStorage.of(context);
/// ```
static PageStorageBucket of(BuildContext context) {
final PageStorage widget = context.ancestorWidgetOfExactType(PageStorage);
return widget?.bucket;
}
@override
Widget build(BuildContext context) => child;
}