restoration_properties.dart 15.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 31 32 33 34 35 36
/// {@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
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 138 139 140 141 142 143 144 145 146 147 148
  @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(T _defaultValue)
    : assert(_defaultValue != null),
      assert(debugIsSerializableForRestoration(_defaultValue)),
      super(_defaultValue);

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

149
  @override
150
  T fromPrimitives(Object? serialized) {
151
    assert(serialized != null);
152
    return super.fromPrimitives(serialized);
153 154 155 156 157
  }

  @override
  Object toPrimitives() {
    assert(value != null);
158
    return super.toPrimitives()!;
159 160 161 162 163
  }
}

/// A [RestorableProperty] that knows how to store and restore a [num].
///
164
/// {@template flutter.widgets.RestorableNum}
165 166 167 168 169 170 171 172 173 174 175
/// 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]).
176 177 178 179
///
/// See also:
///
///  * [RestorableNumN] for the nullable version of this class.
180 181 182
class RestorableNum<T extends num> extends _RestorablePrimitiveValue<T> {
  /// Creates a [RestorableNum].
  ///
183
  /// {@template flutter.widgets.RestorableNum.constructor}
184 185 186 187 188 189 190 191
  /// 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(T defaultValue) : assert(defaultValue != null), super(defaultValue);
}

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

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

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

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

246 247 248 249
/// A [RestorableProperty] that knows how to store and restore a [bool] that is
/// nullable.
///
/// {@macro flutter.widgets.RestorableNum}
250 251 252 253
///
/// See also:
///
///  * [RestorableBool] for the non-nullable version of this class.
254 255 256 257
class RestorableBoolN extends _RestorablePrimitiveValueN<bool?> {
  /// Creates a [RestorableBoolN].
  ///
  /// {@macro flutter.widgets.RestorableNum.constructor}
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
  RestorableBoolN(bool? defaultValue) : 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.
273
class RestorableNumN<T extends num?> extends _RestorablePrimitiveValueN<T> {
274 275 276
  /// Creates a [RestorableNumN].
  ///
  /// {@macro flutter.widgets.RestorableNum.constructor}
277
  RestorableNumN(T defaultValue) : super(defaultValue);
278 279 280 281 282 283 284 285 286 287
}

/// 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.
288
class RestorableDoubleN extends RestorableNumN<double?> {
289
  /// Creates a [RestorableDoubleN].
290
  ///
291 292 293 294 295 296 297 298 299 300 301 302
  /// {@macro flutter.widgets.RestorableNum.constructor}
  RestorableDoubleN(double? defaultValue) : 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.
303
class RestorableIntN extends RestorableNumN<int?> {
304
  /// Creates a [RestorableIntN].
305
  ///
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
  /// {@macro flutter.widgets.RestorableNum.constructor}
  RestorableIntN(int? defaultValue) : 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(String? defaultValue) : super(defaultValue);
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 348 349 350 351
/// 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;
}

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

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

  @override
  void initWithValue(T value) {
    assert(value != null);
    _value?.removeListener(notifyListeners);
    _value = value;
413
    _value!.addListener(notifyListeners);
414 415 416 417 418 419 420 421 422
  }

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

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
/// 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) {
441
    _disposeOldValue();
442 443 444 445 446
    super.initWithValue(value);
  }

  @override
  void dispose() {
447
    _disposeOldValue();
448 449 450
    super.dispose();
  }

451
  void _disposeOldValue() {
452 453 454 455 456 457 458 459
    if (_value != null) {
      // Scheduling a microtask for dispose to give other entities a chance
      // to remove their listeners first.
      scheduleMicrotask(_value!.dispose);
    }
  }
}

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

  @override
  Object toPrimitives() {
    return value.text;
  }
}