dart-lang / dart-lang/language
Property mixins
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
One of the most powerful features in Kotlin is the ability to abstract over properties via delegation to wrapper objects. https://kotlinlang.org/docs/reference/delegated-properties.html
In Kotlin, this allows the reuse of behaviors for getters and setters. Or in Dart terms, it is a mixin that can be applied to an object member. These mixin applications are not like typical mixin applications, since they anonymously "graft" into their targets in an implementation-defined way. They also do not add new members to the class, only overriding existing members or providing new members through property member access.
Real life example of where this would be useful:
```dart
// Allows body-less constructors for passing arguments into the property mixin. Not used in this example.
property mixin AutoAnimationController for AnimationController on TickerProviderStateMixin {
// Property mixin fields are renamed to be a unique but inaccessible field.
// For non-overrides, these fields can be accessed through a new unique syntax: `field::propertyField`
// However, overrides work like normal mixins (e.g. can insert behavior and delegate upwards).
// Note that these are overrides of the class in the on clause, and the implements clause is not allowed.
// Implementations can either implement property mixins as runtime delegates to a separate object (open world?),
// or rewrite the target class to add fields and methods as actual members to reduce GC overhead (closed world/AOT?).
late AnimationController _controller;
AnimationController get => _controller;
// Lack of setter means that the mixin must be applied to a final field.
// In general, the matrix applies:
// | Final | Non-Final
// Getter | OK | Error
// Setter | Error | OK
// Getter + Setter | Warn | OK
// These override methods to delegate and initialize the hidden _controller field.
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class MyCoolState extends State with SingleTickerProviderStateMixin {
final _controller with AutoAnimationController;
// Shortcut syntax for zero-arg property mixins. equivalent to `with AutoAnimationController()`
// mixins with arguments would take the form `with AutoAnimationController(duration: Duration(seconds: 120))`
// In general, mixin arguments can be any expression, with the semantics of a hidden late final variable.
// Implementations are free to optimize in the case where the mixin argument is a compile time constant.
// Mixin arguments with side effects may have undefined evaluation order/timing.
@override
void build(BuildContext context) {
return ...; // Do something cool with _controller!
}
}
```
The code snippet above solves one of Flutter's pain points, by generalizing the cruft needed for AnimationController setup.
Another example, using arguments and public property fields:
```dart
// A property that listens to a Future and updates the [value] property member and marks the state for a rebuild.
property mixin AutoFuture for FutureOr on State {
T value; // Accessible through property member access syntax.
AutoFuture(this.value); // Initial value must be provided (NNBD)
FutureOr? _future;
FutureOr? get => _future;
// If [f] is a value, update [value] and mark for rebuild.
// If [f] is a Future, subscribe.
// When Future completes, if the future hasn't been set again and still mounted,
// set the value and mark for rebuild.
set(FutureOr? f) {
if (!mounted) return;
_future = f;
if (f is T) {
value = f;
setState(() {});
} else if (f is Future) {
f.then((v) {
if (!identical(_future, f)) return;
value = v;
if (!mounted) return;
setState(() {});
});
}
}
}
class MyCoolerState extends State {
final _assetFuture with AutoFuture(null);
@override
void initState() {
super.initState();
_assetFuture = asyncAssetLoadingIncantationsHere();
}
@override
Widget build() => Text(_assetFuture::value ?? 'Asset not loaded yet');
}
```
## So... Why?
In general, Flutter devs face many issues when dealing with asynchronous resources, or animation controllers, or other disposable objects. There are so many ways to write buggy code, and in many cases the way to write it correctly is non-intuitive. While there are IDE integrations for generating things like animation boilerplate, things like properly handling Futures in a State can lead to bugs if proper mounted checks aren't done, this goes for all asynchronous resources really.
Minimizing the amount of duplicate code and relying on a single implementation helps Flutter developers immensely, and giving developers a library of handy property mixins is a step in the right direction.
---------------
cc @rrousselGit, who wrote https://github.com/rrousselGit/flutter_hooks as an alternative way of solving this problem
Contributor guide
Research direction
Start by reviewing the proposed property-mixin semantics and the linked Kotlin delegated-properties reference. Compare the examples, including constructors, getters, setters, overrides, and property-member access, and identify the language and implementation questions that need resolution. Done would require an agreed Dart language design rather than a localized code edit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, kotlin
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100