DEV Community

Cover image for Revolutionize Your Dart Development with the Release of Dart 3.0 🚀
Pablo Discobar
Pablo Discobar

Posted on • Edited on

Revolutionize Your Dart Development with the Release of Dart 3.0 🚀

Unleash the potential of Dart 3.0 and supercharge your development experience with this game-changing 🎮 update. Packed with incredible new features and improvements, this release will take your coding to the next level. Discover the exciting enhancements and learn how to implement them with our comprehensive guide and code examples. Let's explore the world of Dart 3.0!! 🏊‍♀️

Language Enhancements

To start using the fantastic new features in Dart 3.0, update your package's SDK constraint lower bound to 3.0 or higher (sdk: '^3.0.0').

Introducing Records

Records are a groundbreaking addition, enabling you to create anonymous, immutable data structures. Use records to effortlessly group objects, generate composite map keys, or even return multiple values from functions. Check out this example of a record returning two values:

(double x, double y) geoLocation(String name) {
  if (name == 'Nairobi') {
    return (-1.2921, 36.8219);
  } else {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Extra example:

(String, int) userInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['height'] as int);
}
Enter fullscreen mode Exit fullscreen mode

Records can also be used to create complex data structures, such as graphs and trees. This flexibility simplifies data manipulation and allows you to focus on building powerful applications.

Unleash the Power of Pattern Matching

Pattern matching offers an incredibly expressive way to decompose values into their components. Utilize patterns to call getters on objects, access list elements, or extract fields from records. For example, you can destructure the record from the previous example like this:

var (lat, long) = geoLocation('Nairobi');
print('Nairobi is at $lat, $long.');
Enter fullscreen mode Exit fullscreen mode

Pattern matching also enhances switch cases, allowing you to destructure values and evaluate them based on their type or value:

switch (object) {
  case [int a]:
    print('A list with a single integer element $a');
  case ('name', _):
    print('A two-element record whose first field is "name".');
  default: print('Some other object.');
}
Enter fullscreen mode Exit fullscreen mode

Extra example:

List<dynamic> items = [1, 'apple', 3.14];

for (var item in items) {
  switch (item) {
    case int i:
      print('$i is an integer');
      break;
    case String s:
      print('$s is a string');
      break;
    case double d:
      print('$d is a double');
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

In addition to these powerful features, pattern matching enables complex filtering and transformation operations with minimal code. For example, you can use pattern matching to filter out specific elements from a list or map, making it easier to process and manipulate data.

List<Map<String, dynamic>> users = [
  {'id': 1, 'name': 'Alice', 'age': 30},
  {'id': 2, 'name': 'Bob', 'age': 25},
  {'id': 3, 'name': 'Charlie', 'age': 22},
];

List<Map<String, dynamic>> youngUsers = [
  for (var user in users)
    if case (Map<String, dynamic> m when m['age'] < 25) = user user
];

print(youngUsers); // Output: [{'id': 3, 'name': 'Charlie', 'age': 22}]

Enter fullscreen mode Exit fullscreen mode

Switch Expressions for Multi-Way Branching

Switch expressions allow you to use patterns and multi-way branching in situations where statements aren't allowed. This powerful feature simplifies complex conditional expressions, making your code more readable and maintainable.

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

Extra example:

int calculateBonus(int yearsOfService) {
  return switch (yearsOfService) {
    _ when yearsOfService < 5 => 500,
    _ when yearsOfService < 10 => 1000,
    _ when yearsOfService < 15 => 1500,
    _ => 2000,
  };
}

print(calculateBonus(12)); // Output: 1500
Enter fullscreen mode Exit fullscreen mode

Discover If-case Statements and Elements

The new if-case construct compares a value to a pattern, executing the following statement if the pattern matches. This compact syntax simplifies common tasks, such as filtering and transforming lists or maps:

List<int> numbers = [1, 2, 3, 4, 5, 6];
List<int> evenNumbers = [
  for (var number in numbers) if case (int x when x.isEven) = number x
];
print(evenNumbers); // Output: [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

Extra example:

List<String> fruits = ['apple', 'banana', 'orange', 'grape'];
List<String> longFruits = [
  for (var fruit in fruits) if case (String s when s.length > 5) = fruit s
];
print(longFruits); // Output: ['banana', 'orange']
Enter fullscreen mode Exit fullscreen mode

Enhanced Null Safety

Dart 3.0.0 improves null safety by introducing new operators and extensions:

  • The null-assertion operator (!!): Use !! to throw an exception if the expression is null.
  • The null-safe spread operator (...?): Use ...? to safely spread a nullable iterable.
String? nullableString;
String nonNullableString = nullableString !! 'Default value';

List<int?>? nullableList;
List<int> nonNullableList = [0, ...?nullableList, 10];
Enter fullscreen mode Exit fullscreen mode

Extra example:

int? nullableInt;
int nonNullableInt = nullableInt !! (throw SomeCustomException());
Enter fullscreen mode Exit fullscreen mode

Sealed Classes for Compile-Time Safety

When you mark a type as sealed, the compiler ensures that switches on values of that type exhaustively cover every subtype. This allows you to program in an algebraic datatype style with the compile-time safety you expect:

sealed class Amigo {}
class Lucky extends Amigo {}
class Dusty extends Amigo {}
class Ned extends Amigo {}

String lastName(Amigo amigo) =>
    switch (amigo) {
      case Lucky _ => 'Day';
      case Ned _   => 'Nederlander';
    }
Enter fullscreen mode Exit fullscreen mode

In this example, the compiler reports an error because the switch doesn't cover the subclass Dusty. To fix this, simply add the missing case:

String lastName(Amigo amigo) =>
    switch (amigo) {
      case Lucky _ => 'Day';
      case Dusty _ => 'Yankovic';
      case Ned _   => 'Nederlander';
    }
Enter fullscreen mode Exit fullscreen mode

Extra example:

sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}
class Triangle extends Shape {}

void displayShape(Shape shape) {
  switch (shape) {
    case Circle _:
      print('This is a circle.');
      break;
    case Square _:
      print('This is a square.');
      break;
    case Triangle _:
      print('This is a triangle.');
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

Class Modifiers for Greater Control

New modifiers final, interface, base, and mixin provide greater control over how a class or mixin declaration can be used. Although Dart already supported mixins and interfaces, these new modifiers clarify the intent and help prevent misuse.

  • final class: Prevents the class from being subclassed.
final class MyFinalClass {
  ...
}
Enter fullscreen mode Exit fullscreen mode
  • interface: Specifies that the class should be used only as an interface, making it non-instantiable.
interface MyInterface {
  void someMethod();
}
Enter fullscreen mode Exit fullscreen mode
  • base: Indicates that the class is meant to be inherited from and cannot be instantiated directly.
base class MyBaseClass {
  ...
}
Enter fullscreen mode Exit fullscreen mode
  • mixin: Defines a mixin that can be applied to classes, providing extra functionality.
mixin MyMixin {
  void mixinMethod() {
    print('Hello from MyMixin!');
  }
}
Enter fullscreen mode Exit fullscreen mode

Extra example:

interface Animal {
  void speak();
}

class Dog extends Animal {
  @override
  void speak() {
    print('Woof!');
  }
}

class Cat extends Animal {
  @override
  void speak() {
    print('Meow!');
  }
}
Enter fullscreen mode Exit fullscreen mode

Tooling Improvements

Updated Pub.dev Package Scoring

To enhance package discoverability, the pub.dev package scoring algorithm has been updated. The new algorithm now considers package popularity, compatibility, and overall quality, so you can find the best packages for your project with ease.

Advanced Linter

Dart's linter has been upgraded to provide more detailed warnings, suggestions, and error messages, making it even easier to write high-quality, maintainable code.

Debugger Enhancements

The Dart debugger now offers improved performance, better error messages, and more sophisticated breakpoints, giving you a powerful toolset to diagnose and fix issues in your code.

Dart FFI Improvements

Dart's FFI (Foreign Function Interface) library has been revamped, allowing you to call C and C++ code with even greater ease and efficiency. This update includes improved type checking, better memory management, and enhanced performance for a seamless integration experience.


Embrace the power of Dart 3.0.0 and revolutionize your development experience! Utilize the new language features, enhanced tooling, and improved FFI to create more expressive, maintainable, and efficient code. Upgrade today and enjoy the benefits of this incredible release! 🚀

Top comments (0)