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