1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/painting.dart';
6///
7/// @docImport 'framework.dart';
8library;
9
10import 'dart:async';
11
12import 'package:flutter/foundation.dart';
13import 'package:flutter/services.dart';
14
15import 'editable_text.dart';
16import 'restoration.dart';
17
18/// A [RestorableProperty] that makes the wrapped value accessible to the owning
19/// [State] object via the [value] getter and setter.
20///
21/// Whenever a new [value] is set, [didUpdateValue] is called. Subclasses should
22/// call [notifyListeners] from this method if the new value changes what
23/// [toPrimitives] returns.
24///
25/// ## Using a RestorableValue
26///
27/// {@tool dartpad}
28/// A [StatefulWidget] that has a restorable [int] property.
29///
30/// ** See code in examples/api/lib/widgets/restoration_properties/restorable_value.0.dart **
31/// {@end-tool}
32///
33/// ## Creating a subclass
34///
35/// {@tool snippet}
36/// This example shows how to create a new [RestorableValue] subclass,
37/// in this case for the [Duration] class.
38///
39/// ```dart
40/// class RestorableDuration extends RestorableValue<Duration> {
41/// @override
42/// Duration createDefaultValue() => Duration.zero;
43///
44/// @override
45/// void didUpdateValue(Duration? oldValue) {
46/// if (oldValue == null || oldValue.inMicroseconds != value.inMicroseconds) {
47/// notifyListeners();
48/// }
49/// }
50///
51/// @override
52/// Duration fromPrimitives(Object? data) {
53/// if (data != null) {
54/// return Duration(microseconds: data as int);
55/// }
56/// return Duration.zero;
57/// }
58///
59/// @override
60/// Object toPrimitives() {
61/// return value.inMicroseconds;
62/// }
63/// }
64/// ```
65/// {@end-tool}
66///
67/// See also:
68///
69/// * [RestorableProperty], which is the super class of this class.
70/// * [RestorationMixin], to which a [RestorableValue] needs to be registered
71/// in order to work.
72/// * [RestorationManager], which provides an overview of how state restoration
73/// works in Flutter.
74abstract class RestorableValue<T> extends RestorableProperty<T> {
75 /// The current value stored in this property.
76 ///
77 /// A representation of the current value is stored in the restoration data.
78 /// During state restoration, the property will restore the value to what it
79 /// was when the restoration data it is getting restored from was collected.
80 ///
81 /// The [value] can only be accessed after the property has been registered
82 /// with a [RestorationMixin] by calling
83 /// [RestorationMixin.registerForRestoration].
84 T get value {
85 assert(isRegistered);
86 return _value as T;
87 }
88
89 T? _value;
90 set value(T newValue) {
91 assert(isRegistered);
92 if (newValue != _value) {
93 final T? oldValue = _value;
94 _value = newValue;
95 didUpdateValue(oldValue);
96 }
97 }
98
99 @mustCallSuper
100 @override
101 void initWithValue(T value) {
102 _value = value;
103 }
104
105 /// Called whenever a new value is assigned to [value].
106 ///
107 /// The new value can be accessed via the regular [value] getter and the
108 /// previous value is provided as `oldValue`.
109 ///
110 /// Subclasses should call [notifyListeners] from this method, if the new
111 /// value changes what [toPrimitives] returns.
112 @protected
113 void didUpdateValue(T? oldValue);
114}
115
116// _RestorablePrimitiveValueN and its subclasses allows for null values.
117// See [_RestorablePrimitiveValue] for the non-nullable version of this class.
118class _RestorablePrimitiveValueN<T extends Object?> extends RestorableValue<T> {
119 _RestorablePrimitiveValueN(this._defaultValue)
120 : assert(debugIsSerializableForRestoration(_defaultValue)),
121 super();
122
123 final T _defaultValue;
124
125 @override
126 T createDefaultValue() => _defaultValue;
127
128 @override
129 void didUpdateValue(T? oldValue) {
130 assert(debugIsSerializableForRestoration(value));
131 notifyListeners();
132 }
133
134 @override
135 T fromPrimitives(Object? serialized) => serialized as T;
136
137 @override
138 Object? toPrimitives() => value;
139}
140
141// _RestorablePrimitiveValue and its subclasses are non-nullable.
142// See [_RestorablePrimitiveValueN] for the nullable version of this class.
143class _RestorablePrimitiveValue<T extends Object> extends _RestorablePrimitiveValueN<T> {
144 _RestorablePrimitiveValue(super.defaultValue)
145 : assert(debugIsSerializableForRestoration(defaultValue));
146
147 @override
148 set value(T value) {
149 super.value = value;
150 }
151
152 @override
153 T fromPrimitives(Object? serialized) {
154 assert(serialized != null);
155 return super.fromPrimitives(serialized);
156 }
157
158 @override
159 Object toPrimitives() {
160 return super.toPrimitives()!;
161 }
162}
163
164/// A [RestorableProperty] that knows how to store and restore a [num].
165///
166/// {@template flutter.widgets.RestorableNum}
167/// The current [value] of this property is stored in the restoration data.
168/// During state restoration the property is restored to the value it had when
169/// the restoration data it is getting restored from was collected.
170///
171/// If no restoration data is available, [value] is initialized to the
172/// `defaultValue` given in the constructor.
173/// {@endtemplate}
174///
175/// Instead of using the more generic [RestorableNum] directly, consider using
176/// one of the more specific subclasses (e.g. [RestorableDouble] to store a
177/// [double] and [RestorableInt] to store an [int]).
178///
179/// See also:
180///
181/// * [RestorableNumN] for the nullable version of this class.
182class RestorableNum<T extends num> extends _RestorablePrimitiveValue<T> {
183 /// Creates a [RestorableNum].
184 ///
185 /// {@template flutter.widgets.RestorableNum.constructor}
186 /// If no restoration data is available to restore the value in this property
187 /// from, the property will be initialized with the provided `defaultValue`.
188 /// {@endtemplate}
189 RestorableNum(super.defaultValue);
190}
191
192/// A [RestorableProperty] that knows how to store and restore a [double].
193///
194/// {@macro flutter.widgets.RestorableNum}
195///
196/// See also:
197///
198/// * [RestorableDoubleN] for the nullable version of this class.
199class RestorableDouble extends RestorableNum<double> {
200 /// Creates a [RestorableDouble].
201 ///
202 /// {@macro flutter.widgets.RestorableNum.constructor}
203 RestorableDouble(super.defaultValue);
204}
205
206/// A [RestorableProperty] that knows how to store and restore an [int].
207///
208/// {@macro flutter.widgets.RestorableNum}
209///
210/// See also:
211///
212/// * [RestorableIntN] for the nullable version of this class.
213class RestorableInt extends RestorableNum<int> {
214 /// Creates a [RestorableInt].
215 ///
216 /// {@macro flutter.widgets.RestorableNum.constructor}
217 RestorableInt(super.defaultValue);
218}
219
220/// A [RestorableProperty] that knows how to store and restore a [String].
221///
222/// {@macro flutter.widgets.RestorableNum}
223///
224/// See also:
225///
226/// * [RestorableStringN] for the nullable version of this class.
227class RestorableString extends _RestorablePrimitiveValue<String> {
228 /// Creates a [RestorableString].
229 ///
230 /// {@macro flutter.widgets.RestorableNum.constructor}
231 RestorableString(super.defaultValue);
232}
233
234/// A [RestorableProperty] that knows how to store and restore a [bool].
235///
236/// {@macro flutter.widgets.RestorableNum}
237///
238/// See also:
239///
240/// * [RestorableBoolN] for the nullable version of this class.
241class RestorableBool extends _RestorablePrimitiveValue<bool> {
242 /// Creates a [RestorableBool].
243 ///
244 /// {@macro flutter.widgets.RestorableNum.constructor}
245 RestorableBool(super.defaultValue);
246}
247
248/// A [RestorableProperty] that knows how to store and restore a [bool] that is
249/// nullable.
250///
251/// {@macro flutter.widgets.RestorableNum}
252///
253/// See also:
254///
255/// * [RestorableBool] for the non-nullable version of this class.
256class RestorableBoolN extends _RestorablePrimitiveValueN<bool?> {
257 /// Creates a [RestorableBoolN].
258 ///
259 /// {@macro flutter.widgets.RestorableNum.constructor}
260 RestorableBoolN(super.defaultValue);
261}
262
263/// A [RestorableProperty] that knows how to store and restore a [num]
264/// that is nullable.
265///
266/// {@macro flutter.widgets.RestorableNum}
267///
268/// Instead of using the more generic [RestorableNumN] directly, consider using
269/// one of the more specific subclasses (e.g. [RestorableDoubleN] to store a
270/// [double] and [RestorableIntN] to store an [int]).
271///
272/// See also:
273///
274/// * [RestorableNum] for the non-nullable version of this class.
275class RestorableNumN<T extends num?> extends _RestorablePrimitiveValueN<T> {
276 /// Creates a [RestorableNumN].
277 ///
278 /// {@macro flutter.widgets.RestorableNum.constructor}
279 RestorableNumN(super.defaultValue);
280}
281
282/// A [RestorableProperty] that knows how to store and restore a [double]
283/// that is nullable.
284///
285/// {@macro flutter.widgets.RestorableNum}
286///
287/// See also:
288///
289/// * [RestorableDouble] for the non-nullable version of this class.
290class RestorableDoubleN extends RestorableNumN<double?> {
291 /// Creates a [RestorableDoubleN].
292 ///
293 /// {@macro flutter.widgets.RestorableNum.constructor}
294 RestorableDoubleN(super.defaultValue);
295}
296
297/// A [RestorableProperty] that knows how to store and restore an [int]
298/// that is nullable.
299///
300/// {@macro flutter.widgets.RestorableNum}
301///
302/// See also:
303///
304/// * [RestorableInt] for the non-nullable version of this class.
305class RestorableIntN extends RestorableNumN<int?> {
306 /// Creates a [RestorableIntN].
307 ///
308 /// {@macro flutter.widgets.RestorableNum.constructor}
309 RestorableIntN(super.defaultValue);
310}
311
312/// A [RestorableProperty] that knows how to store and restore a [String]
313/// that is nullable.
314///
315/// {@macro flutter.widgets.RestorableNum}
316///
317/// See also:
318///
319/// * [RestorableString] for the non-nullable version of this class.
320class RestorableStringN extends _RestorablePrimitiveValueN<String?> {
321 /// Creates a [RestorableString].
322 ///
323 /// {@macro flutter.widgets.RestorableNum.constructor}
324 RestorableStringN(super.defaultValue);
325}
326
327/// A [RestorableValue] that knows how to save and restore [DateTime].
328///
329/// {@macro flutter.widgets.RestorableNum}.
330class RestorableDateTime extends RestorableValue<DateTime> {
331 /// Creates a [RestorableDateTime].
332 ///
333 /// {@macro flutter.widgets.RestorableNum.constructor}
334 RestorableDateTime(DateTime defaultValue) : _defaultValue = defaultValue;
335
336 final DateTime _defaultValue;
337
338 @override
339 DateTime createDefaultValue() => _defaultValue;
340
341 @override
342 void didUpdateValue(DateTime? oldValue) {
343 assert(debugIsSerializableForRestoration(value.millisecondsSinceEpoch));
344 notifyListeners();
345 }
346
347 @override
348 DateTime fromPrimitives(Object? data) => DateTime.fromMillisecondsSinceEpoch(data! as int);
349
350 @override
351 Object? toPrimitives() => value.millisecondsSinceEpoch;
352}
353
354/// A [RestorableValue] that knows how to save and restore [DateTime] that is
355/// nullable.
356///
357/// {@macro flutter.widgets.RestorableNum}.
358class RestorableDateTimeN extends RestorableValue<DateTime?> {
359 /// Creates a [RestorableDateTime].
360 ///
361 /// {@macro flutter.widgets.RestorableNum.constructor}
362 RestorableDateTimeN(DateTime? defaultValue) : _defaultValue = defaultValue;
363
364 final DateTime? _defaultValue;
365
366 @override
367 DateTime? createDefaultValue() => _defaultValue;
368
369 @override
370 void didUpdateValue(DateTime? oldValue) {
371 assert(debugIsSerializableForRestoration(value?.millisecondsSinceEpoch));
372 notifyListeners();
373 }
374
375 @override
376 DateTime? fromPrimitives(Object? data) =>
377 data != null ? DateTime.fromMillisecondsSinceEpoch(data as int) : null;
378
379 @override
380 Object? toPrimitives() => value?.millisecondsSinceEpoch;
381}
382
383/// A base class for creating a [RestorableProperty] that stores and restores a
384/// [Listenable].
385///
386/// This class may be used to implement a [RestorableProperty] for a
387/// [Listenable], whose information it needs to store in the restoration data
388/// change whenever the [Listenable] notifies its listeners.
389///
390/// The [RestorationMixin] this property is registered with will call
391/// [toPrimitives] whenever the wrapped [Listenable] notifies its listeners to
392/// update the information that this property has stored in the restoration
393/// data.
394abstract class RestorableListenable<T extends Listenable> extends RestorableProperty<T> {
395 /// The [Listenable] stored in this property.
396 ///
397 /// A representation of the current value of the [Listenable] is stored in the
398 /// restoration data. During state restoration, the [Listenable] returned by
399 /// this getter will be restored to the state it had when the restoration data
400 /// the property is getting restored from was collected.
401 ///
402 /// The [value] can only be accessed after the property has been registered
403 /// with a [RestorationMixin] by calling
404 /// [RestorationMixin.registerForRestoration].
405 T get value {
406 assert(isRegistered);
407 return _value!;
408 }
409
410 T? _value;
411
412 @override
413 void initWithValue(T value) {
414 _value?.removeListener(notifyListeners);
415 _value = value;
416 _value!.addListener(notifyListeners);
417 }
418
419 @override
420 void dispose() {
421 super.dispose();
422 _value?.removeListener(notifyListeners);
423 }
424}
425
426/// A base class for creating a [RestorableProperty] that stores and restores a
427/// [ChangeNotifier].
428///
429/// This class may be used to implement a [RestorableProperty] for a
430/// [ChangeNotifier], whose information it needs to store in the restoration
431/// data change whenever the [ChangeNotifier] notifies its listeners.
432///
433/// The [RestorationMixin] this property is registered with will call
434/// [toPrimitives] whenever the wrapped [ChangeNotifier] notifies its listeners
435/// to update the information that this property has stored in the restoration
436/// data.
437///
438/// Furthermore, the property will dispose the wrapped [ChangeNotifier] when
439/// either the property itself is disposed or its value is replaced with another
440/// [ChangeNotifier] instance.
441abstract class RestorableChangeNotifier<T extends ChangeNotifier> extends RestorableListenable<T> {
442 @override
443 void initWithValue(T value) {
444 _disposeOldValue();
445 super.initWithValue(value);
446 }
447
448 @override
449 void dispose() {
450 _disposeOldValue();
451 super.dispose();
452 }
453
454 void _disposeOldValue() {
455 if (_value != null) {
456 // Scheduling a microtask for dispose to give other entities a chance
457 // to remove their listeners first.
458 scheduleMicrotask(_value!.dispose);
459 }
460 }
461}
462
463/// A [RestorableProperty] that knows how to store and restore a
464/// [TextEditingController].
465///
466/// The [TextEditingController] is accessible via the [value] getter. During
467/// state restoration, the property will restore [TextEditingController.text] to
468/// the value it had when the restoration data it is getting restored from was
469/// collected.
470class RestorableTextEditingController extends RestorableChangeNotifier<TextEditingController> {
471 /// Creates a [RestorableTextEditingController].
472 ///
473 /// This constructor treats a null `text` argument as if it were the empty
474 /// string.
475 factory RestorableTextEditingController({String? text}) =>
476 RestorableTextEditingController.fromValue(
477 text == null ? TextEditingValue.empty : TextEditingValue(text: text),
478 );
479
480 /// Creates a [RestorableTextEditingController] from an initial
481 /// [TextEditingValue].
482 ///
483 /// This constructor treats a null `value` argument as if it were
484 /// [TextEditingValue.empty].
485 RestorableTextEditingController.fromValue(TextEditingValue value) : _initialValue = value;
486
487 final TextEditingValue _initialValue;
488
489 @override
490 TextEditingController createDefaultValue() {
491 return TextEditingController.fromValue(_initialValue);
492 }
493
494 @override
495 TextEditingController fromPrimitives(Object? data) {
496 return TextEditingController(text: data! as String);
497 }
498
499 @override
500 Object toPrimitives() {
501 return value.text;
502 }
503}
504
505/// A [RestorableProperty] that knows how to store and restore a nullable [Enum]
506/// type.
507///
508/// {@macro flutter.widgets.RestorableNum}
509///
510/// The values are serialized using the name of the enum, obtained using the
511/// [EnumName.name] extension accessor.
512///
513/// The represented value is accessible via the [value] getter. The set of
514/// values in the enum are accessible via the [values] getter. Since
515/// [RestorableEnumN] allows null, this set will include null.
516///
517/// See also:
518///
519/// * [RestorableEnum], a class similar to this one that knows how to store and
520/// restore non-nullable [Enum] types.
521class RestorableEnumN<T extends Enum> extends RestorableValue<T?> {
522 /// Creates a [RestorableEnumN].
523 ///
524 /// {@macro flutter.widgets.RestorableNum.constructor}
525 RestorableEnumN(T? defaultValue, {required Iterable<T> values})
526 : assert(
527 defaultValue == null || values.contains(defaultValue),
528 'Default value $defaultValue not found in $T values: $values',
529 ),
530 _defaultValue = defaultValue,
531 values = values.toSet();
532
533 @override
534 T? createDefaultValue() => _defaultValue;
535 final T? _defaultValue;
536
537 @override
538 set value(T? newValue) {
539 assert(
540 newValue == null || values.contains(newValue),
541 'Attempted to set an unknown enum value "$newValue" that is not null, or '
542 'in the valid set of enum values for the $T type: '
543 '${values.map<String>((T value) => value.name).toSet()}',
544 );
545 super.value = newValue;
546 }
547
548 /// The set of non-null values that this [RestorableEnumN] may represent.
549 ///
550 /// This is a required field that supplies the enum values that are serialized
551 /// and restored.
552 ///
553 /// If a value is encountered that is not null or a value in this set,
554 /// [fromPrimitives] will assert when restoring.
555 ///
556 /// It is typically set to the `values` list of the enum type.
557 ///
558 /// In addition to this set, because [RestorableEnumN] allows nullable values,
559 /// null is also a valid value, even though it doesn't appear in this set.
560 ///
561 /// {@tool snippet} For example, to create a [RestorableEnumN] with an
562 /// [AxisDirection] enum value, with a default value of null, you would build
563 /// it like the code below:
564 ///
565 /// ```dart
566 /// RestorableEnumN<AxisDirection> axis = RestorableEnumN<AxisDirection>(null, values: AxisDirection.values);
567 /// ```
568 /// {@end-tool}
569 Set<T> values;
570
571 @override
572 void didUpdateValue(T? oldValue) {
573 notifyListeners();
574 }
575
576 @override
577 T? fromPrimitives(Object? data) {
578 if (data == null) {
579 return null;
580 }
581 if (data is String) {
582 for (final T allowed in values) {
583 if (allowed.name == data) {
584 return allowed;
585 }
586 }
587 assert(
588 false,
589 'Attempted to set an unknown enum value "$data" that is not null, or '
590 'in the valid set of enum values for the $T type: '
591 '${values.map<String>((T value) => value.name).toSet()}',
592 );
593 }
594 return _defaultValue;
595 }
596
597 @override
598 Object? toPrimitives() => value?.name;
599}
600
601/// A [RestorableProperty] that knows how to store and restore an [Enum]
602/// type.
603///
604/// {@macro flutter.widgets.RestorableNum}
605///
606/// The values are serialized using the name of the enum, obtained using the
607/// [EnumName.name] extension accessor.
608///
609/// The represented value is accessible via the [value] getter.
610///
611/// See also:
612///
613/// * [RestorableEnumN], a class similar to this one that knows how to store and
614/// restore nullable [Enum] types.
615class RestorableEnum<T extends Enum> extends RestorableValue<T> {
616 /// Creates a [RestorableEnum].
617 ///
618 /// {@macro flutter.widgets.RestorableNum.constructor}
619 RestorableEnum(T defaultValue, {required Iterable<T> values})
620 : assert(
621 values.contains(defaultValue),
622 'Default value $defaultValue not found in $T values: $values',
623 ),
624 _defaultValue = defaultValue,
625 values = values.toSet();
626
627 @override
628 T createDefaultValue() => _defaultValue;
629 final T _defaultValue;
630
631 @override
632 set value(T newValue) {
633 assert(
634 values.contains(newValue),
635 'Attempted to set an unknown enum value "$newValue" that is not in the '
636 'valid set of enum values for the $T type: '
637 '${values.map<String>((T value) => value.name).toSet()}',
638 );
639
640 super.value = newValue;
641 }
642
643 /// The set of values that this [RestorableEnum] may represent.
644 ///
645 /// This is a required field that supplies the possible enum values that can
646 /// be serialized and restored.
647 ///
648 /// If a value is encountered that is not in this set, [fromPrimitives] will
649 /// assert when restoring.
650 ///
651 /// It is typically set to the `values` list of the enum type.
652 ///
653 /// {@tool snippet} For example, to create a [RestorableEnum] with an
654 /// [AxisDirection] enum value, with a default value of [AxisDirection.up],
655 /// you would build it like the code below:
656 ///
657 /// ```dart
658 /// RestorableEnum<AxisDirection> axis = RestorableEnum<AxisDirection>(AxisDirection.up, values: AxisDirection.values);
659 /// ```
660 /// {@end-tool}
661 Set<T> values;
662
663 @override
664 void didUpdateValue(T? oldValue) {
665 notifyListeners();
666 }
667
668 @override
669 T fromPrimitives(Object? data) {
670 if (data != null && data is String) {
671 for (final T allowed in values) {
672 if (allowed.name == data) {
673 return allowed;
674 }
675 }
676 assert(
677 false,
678 'Attempted to restore an unknown enum value "$data" that is not in the '
679 'valid set of enum values for the $T type: '
680 '${values.map<String>((T value) => value.name).toSet()}',
681 );
682 }
683 return _defaultValue;
684 }
685
686 @override
687 Object toPrimitives() => value.name;
688}
689