// Copyright 2014 The Flutter 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 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'editable_text.dart'; import 'restoration.dart'; /// A [RestorableProperty] that makes the wrapped value accessible to the owning /// [State] object via the [value] getter and setter. /// /// Whenever a new [value] is set, [didUpdateValue] is called. Subclasses should /// call [notifyListeners] from this method if the new value changes what /// [toPrimitives] returns. /// /// ## Using a RestorableValue /// /// {@tool dartpad} /// A [StatefulWidget] that has a restorable [int] property. /// /// ** See code in examples/api/lib/widgets/restoration_properties/restorable_value.0.dart ** /// {@end-tool} /// /// ## Creating a subclass /// /// {@tool snippet} /// This example shows how to create a new [RestorableValue] subclass, /// in this case for the [Duration] class. /// /// ```dart /// class RestorableDuration extends RestorableValue<Duration> { /// @override /// Duration createDefaultValue() => Duration.zero; /// /// @override /// void didUpdateValue(Duration? oldValue) { /// if (oldValue == null || oldValue.inMicroseconds != value.inMicroseconds) { /// notifyListeners(); /// } /// } /// /// @override /// Duration fromPrimitives(Object? data) { /// if (data != null) { /// return Duration(microseconds: data as int); /// } /// return Duration.zero; /// } /// /// @override /// Object toPrimitives() { /// return value.inMicroseconds; /// } /// } /// ``` /// {@end-tool} /// /// See also: /// /// * [RestorableProperty], which is the super class of this class. /// * [RestorationMixin], to which a [RestorableValue] needs to be registered /// in order to work. /// * [RestorationManager], which provides an overview of how state restoration /// works in Flutter. abstract class RestorableValue<T> extends RestorableProperty<T> { /// The current value stored in this property. /// /// A representation of the current value is stored in the restoration data. /// During state restoration, the property will restore the value to what it /// was when the restoration data it is getting restored from was collected. /// /// The [value] can only be accessed after the property has been registered /// with a [RestorationMixin] by calling /// [RestorationMixin.registerForRestoration]. T get value { assert(isRegistered); return _value as T; } T? _value; set value(T newValue) { assert(isRegistered); if (newValue != _value) { final T? oldValue = _value; _value = newValue; didUpdateValue(oldValue); } } @mustCallSuper @override void initWithValue(T value) { _value = value; } /// Called whenever a new value is assigned to [value]. /// /// The new value can be accessed via the regular [value] getter and the /// previous value is provided as `oldValue`. /// /// Subclasses should call [notifyListeners] from this method, if the new /// value changes what [toPrimitives] returns. @protected void didUpdateValue(T? oldValue); } // _RestorablePrimitiveValueN and its subclasses allows for null values. // See [_RestorablePrimitiveValue] for the non-nullable version of this class. class _RestorablePrimitiveValueN<T extends Object?> extends RestorableValue<T> { _RestorablePrimitiveValueN(this._defaultValue) : assert(debugIsSerializableForRestoration(_defaultValue)), super(); final T _defaultValue; @override T createDefaultValue() => _defaultValue; @override void didUpdateValue(T? oldValue) { assert(debugIsSerializableForRestoration(value)); notifyListeners(); } @override T fromPrimitives(Object? serialized) => serialized as T; @override Object? toPrimitives() => value; } // _RestorablePrimitiveValue and its subclasses are non-nullable. // See [_RestorablePrimitiveValueN] for the nullable version of this class. class _RestorablePrimitiveValue<T extends Object> extends _RestorablePrimitiveValueN<T> { _RestorablePrimitiveValue(super.defaultValue) : assert(debugIsSerializableForRestoration(defaultValue)); @override set value(T value) { super.value = value; } @override T fromPrimitives(Object? serialized) { assert(serialized != null); return super.fromPrimitives(serialized); } @override Object toPrimitives() { return super.toPrimitives()!; } } /// A [RestorableProperty] that knows how to store and restore a [num]. /// /// {@template flutter.widgets.RestorableNum} /// The current [value] of this property is stored in the restoration data. /// During state restoration the property is restored to the value it had when /// the restoration data it is getting restored from was collected. /// /// If no restoration data is available, [value] is initialized to the /// `defaultValue` given in the constructor. /// {@endtemplate} /// /// Instead of using the more generic [RestorableNum] directly, consider using /// one of the more specific subclasses (e.g. [RestorableDouble] to store a /// [double] and [RestorableInt] to store an [int]). /// /// See also: /// /// * [RestorableNumN] for the nullable version of this class. class RestorableNum<T extends num> extends _RestorablePrimitiveValue<T> { /// Creates a [RestorableNum]. /// /// {@template flutter.widgets.RestorableNum.constructor} /// If no restoration data is available to restore the value in this property /// from, the property will be initialized with the provided `defaultValue`. /// {@endtemplate} RestorableNum(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [double]. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableDoubleN] for the nullable version of this class. class RestorableDouble extends RestorableNum<double> { /// Creates a [RestorableDouble]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableDouble(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore an [int]. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableIntN] for the nullable version of this class. class RestorableInt extends RestorableNum<int> { /// Creates a [RestorableInt]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableInt(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [String]. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableStringN] for the nullable version of this class. class RestorableString extends _RestorablePrimitiveValue<String> { /// Creates a [RestorableString]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableString(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [bool]. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableBoolN] for the nullable version of this class. class RestorableBool extends _RestorablePrimitiveValue<bool> { /// Creates a [RestorableBool]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableBool(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [bool] that is /// nullable. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableBool] for the non-nullable version of this class. class RestorableBoolN extends _RestorablePrimitiveValueN<bool?> { /// Creates a [RestorableBoolN]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableBoolN(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [num] /// that is nullable. /// /// {@macro flutter.widgets.RestorableNum} /// /// Instead of using the more generic [RestorableNumN] directly, consider using /// one of the more specific subclasses (e.g. [RestorableDoubleN] to store a /// [double] and [RestorableIntN] to store an [int]). /// /// See also: /// /// * [RestorableNum] for the non-nullable version of this class. class RestorableNumN<T extends num?> extends _RestorablePrimitiveValueN<T> { /// Creates a [RestorableNumN]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableNumN(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [double] /// that is nullable. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableDouble] for the non-nullable version of this class. class RestorableDoubleN extends RestorableNumN<double?> { /// Creates a [RestorableDoubleN]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableDoubleN(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore an [int] /// that is nullable. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableInt] for the non-nullable version of this class. class RestorableIntN extends RestorableNumN<int?> { /// Creates a [RestorableIntN]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableIntN(super.defaultValue); } /// A [RestorableProperty] that knows how to store and restore a [String] /// that is nullable. /// /// {@macro flutter.widgets.RestorableNum} /// /// See also: /// /// * [RestorableString] for the non-nullable version of this class. class RestorableStringN extends _RestorablePrimitiveValueN<String?> { /// Creates a [RestorableString]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableStringN(super.defaultValue); } /// A [RestorableValue] that knows how to save and restore [DateTime]. /// /// {@macro flutter.widgets.RestorableNum}. class RestorableDateTime extends RestorableValue<DateTime> { /// Creates a [RestorableDateTime]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableDateTime(DateTime defaultValue) : _defaultValue = defaultValue; final DateTime _defaultValue; @override DateTime createDefaultValue() => _defaultValue; @override void didUpdateValue(DateTime? oldValue) { assert(debugIsSerializableForRestoration(value.millisecondsSinceEpoch)); notifyListeners(); } @override DateTime fromPrimitives(Object? data) => DateTime.fromMillisecondsSinceEpoch(data! as int); @override Object? toPrimitives() => value.millisecondsSinceEpoch; } /// A [RestorableValue] that knows how to save and restore [DateTime] that is /// nullable. /// /// {@macro flutter.widgets.RestorableNum}. class RestorableDateTimeN extends RestorableValue<DateTime?> { /// Creates a [RestorableDateTime]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableDateTimeN(DateTime? defaultValue) : _defaultValue = defaultValue; final DateTime? _defaultValue; @override DateTime? createDefaultValue() => _defaultValue; @override void didUpdateValue(DateTime? oldValue) { assert(debugIsSerializableForRestoration(value?.millisecondsSinceEpoch)); notifyListeners(); } @override DateTime? fromPrimitives(Object? data) => data != null ? DateTime.fromMillisecondsSinceEpoch(data as int) : null; @override Object? toPrimitives() => value?.millisecondsSinceEpoch; } /// A base class for creating a [RestorableProperty] that stores and restores a /// [Listenable]. /// /// This class may be used to implement a [RestorableProperty] for a /// [Listenable], whose information it needs to store in the restoration data /// change whenever the [Listenable] notifies its listeners. /// /// The [RestorationMixin] this property is registered with will call /// [toPrimitives] whenever the wrapped [Listenable] notifies its listeners to /// update the information that this property has stored in the restoration /// data. abstract class RestorableListenable<T extends Listenable> extends RestorableProperty<T> { /// The [Listenable] stored in this property. /// /// A representation of the current value of the [Listenable] is stored in the /// restoration data. During state restoration, the [Listenable] returned by /// this getter will be restored to the state it had when the restoration data /// the property is getting restored from was collected. /// /// The [value] can only be accessed after the property has been registered /// with a [RestorationMixin] by calling /// [RestorationMixin.registerForRestoration]. T get value { assert(isRegistered); return _value!; } T? _value; @override void initWithValue(T value) { _value?.removeListener(notifyListeners); _value = value; _value!.addListener(notifyListeners); } @override void dispose() { super.dispose(); _value?.removeListener(notifyListeners); } } /// A base class for creating a [RestorableProperty] that stores and restores a /// [ChangeNotifier]. /// /// This class may be used to implement a [RestorableProperty] for a /// [ChangeNotifier], whose information it needs to store in the restoration /// data change whenever the [ChangeNotifier] notifies its listeners. /// /// The [RestorationMixin] this property is registered with will call /// [toPrimitives] whenever the wrapped [ChangeNotifier] notifies its listeners /// to update the information that this property has stored in the restoration /// data. /// /// Furthermore, the property will dispose the wrapped [ChangeNotifier] when /// either the property itself is disposed or its value is replaced with another /// [ChangeNotifier] instance. abstract class RestorableChangeNotifier<T extends ChangeNotifier> extends RestorableListenable<T> { @override void initWithValue(T value) { _disposeOldValue(); super.initWithValue(value); } @override void dispose() { _disposeOldValue(); super.dispose(); } void _disposeOldValue() { if (_value != null) { // Scheduling a microtask for dispose to give other entities a chance // to remove their listeners first. scheduleMicrotask(_value!.dispose); } } } /// A [RestorableProperty] that knows how to store and restore a /// [TextEditingController]. /// /// The [TextEditingController] is accessible via the [value] getter. During /// state restoration, the property will restore [TextEditingController.text] to /// the value it had when the restoration data it is getting restored from was /// collected. class RestorableTextEditingController extends RestorableChangeNotifier<TextEditingController> { /// Creates a [RestorableTextEditingController]. /// /// This constructor treats a null `text` argument as if it were the empty /// string. factory RestorableTextEditingController({String? text}) => RestorableTextEditingController.fromValue( text == null ? TextEditingValue.empty : TextEditingValue(text: text), ); /// Creates a [RestorableTextEditingController] from an initial /// [TextEditingValue]. /// /// This constructor treats a null `value` argument as if it were /// [TextEditingValue.empty]. RestorableTextEditingController.fromValue(TextEditingValue value) : _initialValue = value; final TextEditingValue _initialValue; @override TextEditingController createDefaultValue() { return TextEditingController.fromValue(_initialValue); } @override TextEditingController fromPrimitives(Object? data) { return TextEditingController(text: data! as String); } @override Object toPrimitives() { return value.text; } } /// A [RestorableProperty] that knows how to store and restore a nullable [Enum] /// type. /// /// {@macro flutter.widgets.RestorableNum} /// /// The values are serialized using the name of the enum, obtained using the /// [EnumName.name] extension accessor. /// /// The represented value is accessible via the [value] getter. The set of /// values in the enum are accessible via the [values] getter. Since /// [RestorableEnumN] allows null, this set will include null. /// /// See also: /// /// * [RestorableEnum], a class similar to this one that knows how to store and /// restore non-nullable [Enum] types. class RestorableEnumN<T extends Enum> extends RestorableValue<T?> { /// Creates a [RestorableEnumN]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableEnumN(T? defaultValue, { required Iterable<T> values }) : assert(defaultValue == null || values.contains(defaultValue), 'Default value $defaultValue not found in $T values: $values'), _defaultValue = defaultValue, values = values.toSet(); @override T? createDefaultValue() => _defaultValue; final T? _defaultValue; @override set value(T? newValue) { assert(newValue == null || values.contains(newValue), 'Attempted to set an unknown enum value "$newValue" that is not null, or ' 'in the valid set of enum values for the $T type: ' '${values.map<String>((T value) => value.name).toSet()}'); super.value = newValue; } /// The set of non-null values that this [RestorableEnumN] may represent. /// /// This is a required field that supplies the enum values that are serialized /// and restored. /// /// If a value is encountered that is not null or a value in this set, /// [fromPrimitives] will assert when restoring. /// /// It is typically set to the `values` list of the enum type. /// /// In addition to this set, because [RestorableEnumN] allows nullable values, /// null is also a valid value, even though it doesn't appear in this set. /// /// {@tool snippet} For example, to create a [RestorableEnumN] with an /// [AxisDirection] enum value, with a default value of null, you would build /// it like the code below: /// /// ```dart /// RestorableEnumN<AxisDirection> axis = RestorableEnumN<AxisDirection>(null, values: AxisDirection.values); /// ``` /// {@end-tool} Set<T> values; @override void didUpdateValue(T? oldValue) { notifyListeners(); } @override T? fromPrimitives(Object? data) { if (data == null) { return null; } if (data is String) { for (final T allowed in values) { if (allowed.name == data) { return allowed; } } assert(false, 'Attempted to set an unknown enum value "$data" that is not null, or ' 'in the valid set of enum values for the $T type: ' '${values.map<String>((T value) => value.name).toSet()}'); } return _defaultValue; } @override Object? toPrimitives() => value?.name; } /// A [RestorableProperty] that knows how to store and restore an [Enum] /// type. /// /// {@macro flutter.widgets.RestorableNum} /// /// The values are serialized using the name of the enum, obtained using the /// [EnumName.name] extension accessor. /// /// The represented value is accessible via the [value] getter. /// /// See also: /// /// * [RestorableEnumN], a class similar to this one that knows how to store and /// restore nullable [Enum] types. class RestorableEnum<T extends Enum> extends RestorableValue<T> { /// Creates a [RestorableEnum]. /// /// {@macro flutter.widgets.RestorableNum.constructor} RestorableEnum(T defaultValue, { required Iterable<T> values }) : assert(values.contains(defaultValue), 'Default value $defaultValue not found in $T values: $values'), _defaultValue = defaultValue, values = values.toSet(); @override T createDefaultValue() => _defaultValue; final T _defaultValue; @override set value(T newValue) { assert(values.contains(newValue), 'Attempted to set an unknown enum value "$newValue" that is not in the ' 'valid set of enum values for the $T type: ' '${values.map<String>((T value) => value.name).toSet()}'); super.value = newValue; } /// The set of values that this [RestorableEnum] may represent. /// /// This is a required field that supplies the possible enum values that can /// be serialized and restored. /// /// If a value is encountered that is not in this set, [fromPrimitives] will /// assert when restoring. /// /// It is typically set to the `values` list of the enum type. /// /// {@tool snippet} For example, to create a [RestorableEnum] with an /// [AxisDirection] enum value, with a default value of [AxisDirection.up], /// you would build it like the code below: /// /// ```dart /// RestorableEnum<AxisDirection> axis = RestorableEnum<AxisDirection>(AxisDirection.up, values: AxisDirection.values); /// ``` /// {@end-tool} Set<T> values; @override void didUpdateValue(T? oldValue) { notifyListeners(); } @override T fromPrimitives(Object? data) { if (data != null && data is String) { for (final T allowed in values) { if (allowed.name == data) { return allowed; } } assert(false, 'Attempted to restore an unknown enum value "$data" that is not in the ' 'valid set of enum values for the $T type: ' '${values.map<String>((T value) => value.name).toSet()}'); } return _defaultValue; } @override Object toPrimitives() => value.name; }