diff --git a/src/content/language/records.md b/src/content/language/records.md index 02e481f104..fb3277eb85 100644 --- a/src/content/language/records.md +++ b/src/content/language/records.md @@ -95,7 +95,7 @@ recordAB = recordXY; // OK. ``` This is similar to how positional parameters -in a function declaration or function typedef +in a [function declaration or function typedef][function-type] can have names but those names don't affect the signature of the function. For more information and examples, check out @@ -220,10 +220,128 @@ parallelization of futures of different types, which you can read about in the [`dart:async` documentation][]. ::: +## Records as simple data structures + +Records only hold data. When that's all you need, +they're immediately available and easy to use +without needing to declare any new classes. +For a simple list of data tuples that all have the same shape, +a *list of records* is the most direct representation. + +Take this list of "button definitions", for example: + +```dart +final buttons = [ + ( + label: "Button I", + icon: const Icon(Icons.upload_file), + onPressed: () => print("Action -> Button I"), + ), + ( + label: "Button II", + icon: const Icon(Icons.info), + onPressed: () => print("Action -> Button II"), + ) +]; +``` + +This code can be written directly without needing any additional declarations. + +### Records and typedefs + +You can choose to use [typedefs][] to give the record type itself a name, +and use that rather than writing out the full record type. +This method allows you to state that some fields can be null (`?`), +even if none of the current entries in the list have a null value. + +```dart +typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed}); +final List buttons = [ + // ... +]; +``` + +Because record types are structural types, giving a name like `ButtonItem` +only introduces an alias that makes it easier to refer to the structural type: +`({String label, Icon icon, void Function()? onPressed})`. + +Having all your code refer to a record type by its alias makes it easier to +later change the record's implementation without needing to update every reference. + +Code can work with the given button definitions the same way it would +with simple class instances: + +```dart + List widget = [ + for (var button in buttons) + Container( + margin: const EdgeInsets.all(4.0), + child: OutlinedButton.icon( + onPressed: button.onPressed, + icon: button.icon, + label: Text(button.label), + ), + ), + ]; +``` + +You could even decide to later change the record type to a class type to add methods: + +```dart +class ButtonItem { + final String label; + final Icon icon; + final void Function()? onPressed; + ButtonItem({required this.label, required this.icon, this.onPressed}); + bool get hasOnpressed => onPressed != null; +} +``` + +Or to an [extension type][]: + +```dart +extension type ButtonItem._(({String label, Icon icon, void Function()? onPressed}) _) { + String get label => _.label; + Icon get icon => _.icon; + void Function()? get onPressed => _.onPressed; + ButtonItem({required String label, required Icon icon, void Function()? onPressed}) + : this._((label: label, icon: icon, onPressed: onPressed)); + bool get hasOnpressed => _.onPressed != null; +} +``` + +And then create the list of button definitions using that type's constructors: + +```dart +final List buttons = [ + ButtonItem( + label: "Button I", + icon: const Icon(Icons.upload_file), + onPressed: () => print("Action -> Button I"), + ), + ButtonItem( + label: "Button II", + icon: const Icon(Icons.info), + onPressed: () => print("Action -> Button II"), + ) +]; +``` + +Again, all while not needing to change the code that uses that list. + +Changing any type does require the code using it to be very careful about +not making assumptions. A type alias does not offer any protection or guarantee, +for the code using it as a reference, that the value being aliased is a record. +Extension types, also, offer little protection. +Only a class can provide full abstraction and encapsulation. + [language version]: /resources/language/evolution#language-versioning [collection types]: /language/collections [pattern]: /language/patterns#destructuring-multiple-returns [`dart:async` documentation]: /libraries/dart-async#handling-errors-for-multiple-futures [parameters and arguments]: /language/functions#parameters +[function-type]: /language/functions#function-types [destructure]: /language/patterns#destructuring [Pattern types]: /language/pattern-types#record +[typedefs]: /language/typedefs +[extension type]: /language/extension-types \ No newline at end of file