| // 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 |
| /// new TabBarView( |
| /// children: myTabs.map((Tab tab) { |
| /// new MyScrollableTabView( |
| /// key: new 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 new _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; |
| } |