This paper continues to analyze the various keys in FLUTTER
The types of key
@immutable
abstract class Key {
/// Construct a [ValueKey<String>] with the given [String].
///
/// This is the simplest way to create keys.
const factory Key(String value) = ValueKey<String>;
/// Default constructor, used by subclasses.
///
/// Useful so that subclasses can call us, because the [new Key] factory
/// constructor shadows the implicit constructor.
@protected
const Key.empty();
}
Copy the code
The default key creates a ValueKey from the key passed in by the factory method. Key is derived from two different types of keys: LocalKey and GlobalKey
GlobalKey
- GlobalKey uniquely identifies Elements and provides access to Elements associated with BuildContext, State (for StatefulWidget)
- Do not use the same GlobalKey in two widgets
- Global keys are expensive. Use [Key], [ValueKey], [ObjectKey], or [UniqueKey] if you don’t need to access BuildContext, Element, and State.
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
/// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
/// debugging.
/// The label is purely for debugging and not used for comparing the identity
/// of the key.
factory GlobalKey({ String debugLabel }) => LabeledGlobalKey<T>(debugLabel);
/// Creates a global key without a label.
/// Used by subclasses because the factory constructor shadows the implicit
/// constructor.
const GlobalKey.constructor() : super.empty();
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element> {};void _register(Element element) {
_registry[this] = element;
}
void _unregister(Element element) {
if (_registry[this] == element)
_registry.remove(this);
}
Element get _currentElement => _registry[this];
/// The build context in which the widget with this key builds.
/// The current context is null if there is no widget in the tree that matches
/// this global key.
BuildContext get currentContext => _currentElement;
/// The widget in the tree that currently has this global key.
/// The current widget is null if there is no widget in the tree that matches
/// this global key.
Widget getcurrentContext => _currentElement? .widget;/// The [State] for the widget in the tree that currently has this global key.
/// The current state is null if (1) there is no widget in the tree that
/// matches this global key, (2) that widget is not a [StatefulWidget], or the
/// associated [State] object is not a subtype of `T`.
T get currentState {
final Element element = _currentElement;
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
final State state = statefulElement.state;
if (state is T)
return state;
}
return null; }}Copy the code
GlobalKey uses a static constant Map to hold its corresponding Element. GlobalKey allows you to find the Widget, State, and Element that holds the GlobalKey. They allow widgets to change their parent anywhere in the app without losing State, and there are several properties inside that mean you can access currentContext, currentContext, and currentState (if StatefullWidget), This is done by calling _register when an Element is mounted to the tree. If the Element is of type GlobalKey, it will be added to a static Map, and _unregister is removed when unmounted
- For example, if you use the same Widget on different screens but keep the same State, you need to use GlobalKeys
class SwitcherScreenState extends State<SwitcherScreen> {
bool isActive = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Switch.adaptive(
value: isActive,
onChanged: (boolcurrentStatus) { isActive = currentStatus; setState(() {}); }),),); } changeState() { isActive = ! isActive; setState(() {}); }}Copy the code
But if we want to change this state externally, we need to use GlobalKey
class _ScreenState extends State<Screen> {
final GlobalKey<SwitcherScreenState> key = GlobalKey<SwitcherScreenState>();
@override
Widget build(BuildContext context) {
returnScaffold( body: SwitcherScreen( key: key, ), floatingActionButton: FloatingActionButton(onPressed: () { key.currentState.changeState(); })); }}Copy the code
In general, GlobalKey looks a bit like a global variable. There are better ways to find state, too, such as inheritedWidgets, Redux, or Block patterns.
Localkey
LocalKey inherits directly from Key and is used when comparing widgets that have the same parent Element. In the example above, you should use LocalKey when you have a multi-child Widget that needs to move its child widgets. Localkey has a number of subclasses of key:
- ValueKey : ValueKey(‘String’)
- ObjectKey : ObjectKey(Object)
- UniqueKey : UniqueKey()
Valuekey derives PageStorageKey: PageStorageKey(‘value’)
- ValueKey
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
/// The value to which this key delegates its [operator==]
final T value;
@override
bool operator= = (dynamic other) {
if(other.runtimeType ! = runtimeType)return false;
final ValueKey<T> typedOther = other;
return value == typedOther.value;
}
@override
int get hashCode => hashValues(runtimeType, value);
}
Copy the code
Derived from LocalKey, accepts a generic class that overrides both the == and hash methods, and requires both runtimeType and value validation
Usage scenarios
If you have a Todo List application, it will record what you need Todo. Let’s say each Todo thing is different, and you want to swipe delete on each Todo. In this case, use **ValueKey.
- PageStoreKey
class PageStorageKey<T> extends ValueKey<T> {
/// Creates a [ValueKey] that defines where [PageStorage] values will be saved.
const PageStorageKey(T value) : super(value);
}
Copy the code
The PageStorageKey inherits from ValueKey, passing in a value, which is the offset that holds the Scrollable. Scrollable(actually ScrollPositions) uses the PageStorage to hold their scroll offset, and every time a scroll is done, The stored scrolling information is updated. PageStorage is used to save and restore values that are longer than the lifetime of the widget. These values are stored in a Per-route Map, and their keys are defined by the Widget and its ancestors’ PageStorageKeys
Usage scenarios
When you have a sliding list, you jump from one Item to a new page, and when you go back to the previous list page, you find that the sliding distance is back to the top. At this point, give the Sliver a **PageStorageKey ** that will keep the Sliver scrolling
- ObjectKey
class ObjectKey extends LocalKey {
/// Creates a key that uses [identical] on [value] for its [operator==].
const ObjectKey(this.value);
/// The object whose identity is used by this key's [operator==].
final Object value;
@override
bool operator= = (dynamic other) {
if(other.runtimeType ! = runtimeType)return false;
final ObjectKey typedOther = other;
return identical(value, typedOther.value);
}
@override
int get hashCode => hashValues(runtimeType, identityHashCode(value));
}
Copy the code
The identical constructor also needs to pass in a value that is of type Object, meaning it can be passed to any type. The identical method returns whether the hashcodes of two objects are equal. ObjectKey is considered equal only if both runtimeType and value.hashcode are equal. This differs from ValueKey in that it compares references to value, whereas ValueKey compares values directly
Usage scenarios
If you have a birthday app that records someone’s birthday and displays it in a list, there’s also a swipe to delete. We know that the name may be repeated, and there’s no guarantee that you’ll give the Key a different value every time. However, an Object will be unique when the name and birthday are combined. You need to use ObjectKey!
- UniqueKey
/// A key that is only equal to itself.
class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();
}
Copy the code
If none of the combined objects can be unique, you want to make sure that each Key is unique. Well, you can use UniqueKey. It will generate a unique hash code from the object. By doing so, a new UniqueKey will be generated every time the Widget is built, losing consistency