page_storage.dart 5.67 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/foundation.dart';
6

7 8
import 'framework.dart';

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
/// 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 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);
}
40

41
class _StorageEntryIdentifier {
42 43
  _StorageEntryIdentifier(this.keys)
    : assert(keys != null);
44

45
  final List<PageStorageKey<dynamic>> keys;
46

47 48
  bool get isNotEmpty => keys.isNotEmpty;

49
  @override
50
  bool operator ==(dynamic other) {
51
    if (other.runtimeType != runtimeType)
52 53
      return false;
    final _StorageEntryIdentifier typedOther = other;
54 55 56
    for (int index = 0; index < keys.length; index += 1) {
      if (keys[index] != typedOther.keys[index])
        return false;
57 58 59
    }
    return true;
  }
60 61

  @override
62
  int get hashCode => hashList(keys);
63

64
  @override
65
  String toString() {
66
    return 'StorageEntryIdentifier(${keys?.join(":")})';
67
  }
68 69
}

70 71 72 73
/// 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.
74
class PageStorageBucket {
75 76 77 78 79 80 81 82 83 84 85
  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)) {
86
      context.visitAncestorElements((Element element) {
87
        return _maybeAddKey(element, keys);
88 89
      });
    }
90 91 92 93
    return keys;
  }

  _StorageEntryIdentifier _computeIdentifier(BuildContext context) {
94
    return new _StorageEntryIdentifier(_allKeys(context));
95 96
  }

97
  Map<Object, dynamic> _storage;
98

99 100 101 102 103
  /// 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.
104
  ///
105 106
  /// If an explicit identifier is not provided and no [PageStorageKey]s
  /// are found, then the `data` is not saved.
107 108
  void writeState(BuildContext context, dynamic data, { Object identifier }) {
    _storage ??= <Object, dynamic>{};
109 110 111 112 113 114 115
    if (identifier != null) {
      _storage[identifier] = data;
    } else {
      final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
      if (contextIdentifier.isNotEmpty)
        _storage[contextIdentifier] = data;
    }
116
  }
117

118 119 120 121 122 123 124 125
  /// 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.
126
  dynamic readState(BuildContext context, { Object identifier }) {
127 128 129 130 131 132
    if (_storage == null)
      return null;
    if (identifier != null)
      return _storage[identifier];
    final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
    return contextIdentifier.isNotEmpty ? _storage[contextIdentifier] : null;
133 134 135
  }
}

136
/// A widget that establishes a page storage bucket for this widget subtree.
137
class PageStorage extends StatelessWidget {
138 139 140
  /// Creates a widget that provides a storage bucket for its descendants.
  ///
  /// The [bucket] argument must not be null.
141
  const PageStorage({
142
    Key key,
143
    @required this.bucket,
144
    @required this.child
145 146
  }) : assert(bucket != null),
       super(key: key);
147

148
  /// The widget below this widget in the tree.
149 150
  ///
  /// {@macro flutter.widgets.child}
151
  final Widget child;
152 153

  /// The page storage bucket to use for this subtree.
154 155
  final PageStorageBucket bucket;

156 157
  /// The bucket from the closest instance of this class that encloses the given context.
  ///
158
  /// Returns null if none exists.
159 160 161 162 163 164
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// PageStorageBucket bucket = PageStorage.of(context);
  /// ```
165
  static PageStorageBucket of(BuildContext context) {
166
    final PageStorage widget = context.ancestorWidgetOfExactType(PageStorage);
Hixie's avatar
Hixie committed
167
    return widget?.bucket;
168 169
  }

170
  @override
171 172
  Widget build(BuildContext context) => child;
}