restoration_properties.dart 20.8 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'dart:async';

7 8 9 10 11 12 13 14 15 16 17 18 19
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.
///
20 21
/// ## Using a RestorableValue
///
22
/// {@tool dartpad}
23 24
/// A [StatefulWidget] that has a restorable [int] property.
///
25
/// ** See code in examples/api/lib/widgets/restoration_properties/restorable_value.0.dart **
26 27 28 29 30
/// {@end-tool}
///
/// ## Creating a subclass
///
/// {@tool snippet}
31
/// This example shows how to create a new [RestorableValue] subclass,
32 33 34 35 36
/// in this case for the [Duration] class.
///
/// ```dart
/// class RestorableDuration extends RestorableValue<Duration> {
///   @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 48 49 50
///   Duration fromPrimitives(Object? data) {
///     if (data != null) {
///       return Duration(microseconds: data as int);
///     }
51
///     return Duration.zero;
52 53 54 55 56 57 58 59 60 61
///   }
///
///   @override
///   Object toPrimitives() {
///     return value.inMicroseconds;
///   }
/// }
/// ```
/// {@end-tool}
///
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
/// 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);
81
    return _value as T;
82
  }
83
  T? _value;
84 85 86
  set value(T newValue) {
    assert(isRegistered);
    if (newValue != _value) {
87
      final T? oldValue = _value;
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
      _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
107
  void didUpdateValue(T? oldValue);
108 109
}

110 111 112 113 114
// _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)),
115 116 117 118 119 120 121 122
      super();

  final T _defaultValue;

  @override
  T createDefaultValue() => _defaultValue;

  @override
123
  void didUpdateValue(T? oldValue) {
124 125 126 127
    assert(debugIsSerializableForRestoration(value));
    notifyListeners();
  }

128 129 130 131 132 133 134 135 136 137
  @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> {
138
  _RestorablePrimitiveValue(super.defaultValue)
139
    : assert(debugIsSerializableForRestoration(defaultValue));
140 141 142 143 144 145

  @override
  set value(T value) {
    super.value = value;
  }

146
  @override
147
  T fromPrimitives(Object? serialized) {
148
    assert(serialized != null);
149
    return super.fromPrimitives(serialized);
150 151 152 153
  }

  @override
  Object toPrimitives() {
154
    return super.toPrimitives()!;
155 156 157 158 159
  }
}

/// A [RestorableProperty] that knows how to store and restore a [num].
///
160
/// {@template flutter.widgets.RestorableNum}
161 162 163 164 165 166 167 168 169 170 171
/// 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]).
172 173 174 175
///
/// See also:
///
///  * [RestorableNumN] for the nullable version of this class.
176 177 178
class RestorableNum<T extends num> extends _RestorablePrimitiveValue<T> {
  /// Creates a [RestorableNum].
  ///
179
  /// {@template flutter.widgets.RestorableNum.constructor}
180 181 182
  /// If no restoration data is available to restore the value in this property
  /// from, the property will be initialized with the provided `defaultValue`.
  /// {@endtemplate}
183
  RestorableNum(super.defaultValue);
184 185 186 187
}

/// A [RestorableProperty] that knows how to store and restore a [double].
///
188
/// {@macro flutter.widgets.RestorableNum}
189 190 191 192
///
/// See also:
///
///  * [RestorableDoubleN] for the nullable version of this class.
193 194 195
class RestorableDouble extends RestorableNum<double> {
  /// Creates a [RestorableDouble].
  ///
196
  /// {@macro flutter.widgets.RestorableNum.constructor}
197
  RestorableDouble(super.defaultValue);
198 199 200 201
}

/// A [RestorableProperty] that knows how to store and restore an [int].
///
202
/// {@macro flutter.widgets.RestorableNum}
203 204 205 206
///
/// See also:
///
///  * [RestorableIntN] for the nullable version of this class.
207 208 209
class RestorableInt extends RestorableNum<int> {
  /// Creates a [RestorableInt].
  ///
210
  /// {@macro flutter.widgets.RestorableNum.constructor}
211
  RestorableInt(super.defaultValue);
212 213 214 215
}

/// A [RestorableProperty] that knows how to store and restore a [String].
///
216
/// {@macro flutter.widgets.RestorableNum}
217 218 219 220
///
/// See also:
///
///  * [RestorableStringN] for the nullable version of this class.
221 222 223
class RestorableString extends _RestorablePrimitiveValue<String> {
  /// Creates a [RestorableString].
  ///
224
  /// {@macro flutter.widgets.RestorableNum.constructor}
225
  RestorableString(super.defaultValue);
226 227 228 229
}

/// A [RestorableProperty] that knows how to store and restore a [bool].
///
230
/// {@macro flutter.widgets.RestorableNum}
231 232 233 234
///
/// See also:
///
///  * [RestorableBoolN] for the nullable version of this class.
235 236 237
class RestorableBool extends _RestorablePrimitiveValue<bool> {
  /// Creates a [RestorableBool].
  ///
238
  /// {@macro flutter.widgets.RestorableNum.constructor}
239
  RestorableBool(super.defaultValue);
240 241
}

242 243 244 245
/// A [RestorableProperty] that knows how to store and restore a [bool] that is
/// nullable.
///
/// {@macro flutter.widgets.RestorableNum}
246 247 248 249
///
/// See also:
///
///  * [RestorableBool] for the non-nullable version of this class.
250 251 252 253
class RestorableBoolN extends _RestorablePrimitiveValueN<bool?> {
  /// Creates a [RestorableBoolN].
  ///
  /// {@macro flutter.widgets.RestorableNum.constructor}
254
  RestorableBoolN(super.defaultValue);
255 256 257 258 259 260 261 262 263 264 265 266 267 268
}

/// 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.
269
class RestorableNumN<T extends num?> extends _RestorablePrimitiveValueN<T> {
270 271 272
  /// Creates a [RestorableNumN].
  ///
  /// {@macro flutter.widgets.RestorableNum.constructor}
273
  RestorableNumN(super.defaultValue);
274 275 276 277 278 279 280 281 282 283
}

/// 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.
284
class RestorableDoubleN extends RestorableNumN<double?> {
285
  /// Creates a [RestorableDoubleN].
286
  ///
287
  /// {@macro flutter.widgets.RestorableNum.constructor}
288
  RestorableDoubleN(super.defaultValue);
289 290 291 292 293 294 295 296 297 298
}

/// 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.
299
class RestorableIntN extends RestorableNumN<int?> {
300
  /// Creates a [RestorableIntN].
301
  ///
302
  /// {@macro flutter.widgets.RestorableNum.constructor}
303
  RestorableIntN(super.defaultValue);
304 305 306 307 308 309 310 311 312 313 314 315 316 317
}

/// 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}
318
  RestorableStringN(super.defaultValue);
319 320
}

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
/// 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;
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
/// 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;
}

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
/// 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);
400
    return _value!;
401
  }
402
  T? _value;
403 404 405 406 407

  @override
  void initWithValue(T value) {
    _value?.removeListener(notifyListeners);
    _value = value;
408
    _value!.addListener(notifyListeners);
409 410 411 412 413 414 415 416 417
  }

  @override
  void dispose() {
    super.dispose();
    _value?.removeListener(notifyListeners);
  }
}

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
/// 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) {
436
    _disposeOldValue();
437 438 439 440 441
    super.initWithValue(value);
  }

  @override
  void dispose() {
442
    _disposeOldValue();
443 444 445
    super.dispose();
  }

446
  void _disposeOldValue() {
447 448 449 450 451 452 453 454
    if (_value != null) {
      // Scheduling a microtask for dispose to give other entities a chance
      // to remove their listeners first.
      scheduleMicrotask(_value!.dispose);
    }
  }
}

455 456 457 458 459 460 461
/// 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.
462
class RestorableTextEditingController extends RestorableChangeNotifier<TextEditingController> {
463 464 465 466
  /// Creates a [RestorableTextEditingController].
  ///
  /// This constructor treats a null `text` argument as if it were the empty
  /// string.
467
  factory RestorableTextEditingController({String? text}) => RestorableTextEditingController.fromValue(
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
    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
486 487
  TextEditingController fromPrimitives(Object? data) {
    return TextEditingController(text: data! as String);
488 489 490 491 492 493 494
  }

  @override
  Object toPrimitives() {
    return value.text;
  }
}
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668

/// 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;
}