| // 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(defaultValue != null), |
| assert(debugIsSerializableForRestoration(defaultValue)); |
| |
| @override |
| set value(T value) { |
| assert(value != null); |
| super.value = value; |
| } |
| |
| @override |
| T fromPrimitives(Object? serialized) { |
| assert(serialized != null); |
| return super.fromPrimitives(serialized); |
| } |
| |
| @override |
| Object toPrimitives() { |
| assert(value != null); |
| 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) : assert(defaultValue != null); |
| } |
| |
| /// 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) : assert(defaultValue != null); |
| } |
| |
| /// 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) : assert(defaultValue != null); |
| } |
| |
| /// 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) : assert(defaultValue != null); |
| } |
| |
| /// 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) : assert(defaultValue != null); |
| } |
| |
| /// 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) { |
| assert(value != null); |
| _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; |
| } |
| } |