João Freitas

The following is a summary of the new features introduced to the Dart programming language, now that it hit version 3.0.

https://medium.com/@kvenn/dart-3-0-best-new-features-why-you-should-care-429e739f2690


I’ve seen a few articles that were pretty lengthy, so figured I’d give my abridged take on the best new features coming to Dart and why you should care (in order of my excitement). You should be able to copy and paste each example into DartPad to play around with it. To use Dart 3.0, upgrade Flutter to 3.10.0.

This article covers a quick summary of:

This doesn’t cover everything, so for all the details check out:

Sealed Classes

This example shows how you could wrap a response type and access data or error safely without an instance-of check.

sealed class Response{}  
  
class Success<Type> extends Response {  
  final Type data;  
  Success(this.data);  
}  
  
class Failure extends Response {  
  final Error error;  
  Failure(this.error);  
}  
  
String toString(Response response) => switch (response) {  
      // Can access \`response.data\` without an instance check!  
      Success \_ => response.data.toString(),  
      Failure \_ => response.error.toString(),  
    };  
String toTypeString(Response response) => switch (response) {  
      Success \_ => 'Success',  
      Failure \_ => 'Failure',  
    };

Records (aka n-tuple)

// Function return  
(double lat, double lon) geoLocation(String name) =>  
    (231.23, 36.8219);  
  
void examples() {  
  // Variable declaration / assignment  
  (String, int) record;  
  record = ('A string', 123);  
  
  // Named-args  
  ({int a, bool b}) record;  
  record = (a: 123, b: true);  
  
  // Accessing them!  
  var record = ('first', a: 2, b: true, 'last');  
  print(record.$1); // Prints 'first'  
  print(record.a); // Prints 2  
  print(record.b); // Prints true  
  print(record.$2); // Prints 'last'  
}

Switches inside of widgets (instead of ternary and if()…[])

switch (state) {  
  \_ when state == 'loading' => const Text('Loading...'),  
  \_ when state == 'content' => const Text('My content widget goes here'),  
  // Else case  
  \_ => const Text('Unknown state encountered'),  
},

return TextButton(  
  onPressed: \_goPrevious,  
  child: Text(switch (page) {  
    0 => 'Exit story',  
    1 => 'First page',  
    \_ when page == \_lastPage => 'Start over',  
    \_ => 'Previous page',  
  }),  
);

Destructuring via pattern matching

class Person {  
  String name;  
  int age;  
  Person({this.name = 'John', this.age = 30});  
}  
  
void examples() {  
  // Records  
  final (lat, lon) = geoLocation('Nairobi');  
  // Class  
  final Person person = Person(name: 'John', age: 30);  
  final Person(:name, :age) = person;  
  print('Name $name, age $age');  
  // Lists  
  var numList = \[1, 2, 3\];  
  var \[a, b, c\] = numList;  
}

New list extensions

nonNulls, firstOrNull, lastOrNull, singleOrNull, elementAtOrNull and indexed on Iterables.

final list = \[\]  
// Do this  
final lastElement = list.lastOrNull()  
// Instead of this  
final lastElement = list\[list.length - 1\]

Class modifiers

TL;DR: Use abstract interface for a traditional interface . Use abstract for traditional abstract class. Use final for regular classes if they shouldn’t be overridden.

This table is a simplified version of the full spec table

I already found class modifiers weird in Dart, and now they’re weirder (but in a powerful way!). I’m going to cover only the new stuff.

The first thing you need to get is the difference between implements and extends . These concepts existed in Dart 2.x, but get expanded on.

final

// -- File a.dart  
final class FinalClass {}  
  
// -- File b.dart  
// Not allowed  
class ExtensionClass extends FinalClass{}  
// Not allowed  
class ExtensionClass implements FinalClass{}

interface

// -- File a.dart  
interface class InterfaceClass {  
  String name = 'Dave'; // Allowed  
  void body() { print('body'); } // Allowed  
  
  int get myField; // Not allowed  
  void noBody(); // Not allowed  
} 
// -- File b.dart  
// Not allowed  
class ExtensionClass extends InterfaceClass{}  
// Allowed  
class ConcreteClass implements InterfaceClass{  
  // Have to override everything  
  @override  
  String name = 'ConcreteName';  
  @override  
  void function() { print('body'); }  
}

abstract interface

// -- File a.dart  
abstract interface class AbstractInterfaceClass {  
  String name = 'Dave'; // Allowed  
  void body() { print('body'); } // Allowed  
    
  // This is a more traditional implementation  
  int get myField; // Allowed  
  void noBody(); // Allowed  
}  
  
// -- File b.dart  
// Not allowed  
class ExtensionClass extends AbstractInterfaceClass{}  
// Allowed  
class ConcreteClass implements InterfaceClass{  
  // Have to override everything  
  @override  
  String name = 'ConcreteName';  
  @override  
  void function() { print('body'); }  
  
  @override  
  int get myField => 5  
  @override  
  void noBody() = print('concreteBody')  
}

abstract class

// -- File a.dart  
abstract class AbstractClass {  
  String name = 'Dave'; // Allowed  
  void body() { print('body'); } // Allowed  
  
  int get myField; // Allowed  
  void noBody(); // Allowed  
}  
  
// -- File b.dart  
// Allowed!  
class ExtensionClass extends AbstractClass{...}  
// Allowed  
class ConcreteClass implements AbstractClass {  
  // Only have to override things with no body  
  @override  
  int get myField => 5  
  @override  
  void noBody() = print('concreteBody')  
}

#reads #kyle venn #dart #summary