Dart is a versatile, object-oriented, class-based programming language designed for building high-quality web, mobile, and desktop applications. Key features include a clean syntax, strong typing, garbage collection, and the ability to compile to both native code and JavaScript. Dart 2 introduced extensions, enums, mixins, and generics for improved code reusability and type safety. It supports asynchronous programming with Future and Stream classes, making it suitable for managing concurrent operations. With these capabilities, Dart is widely used with Flutter for building performant, cross-platform applications.
Introducing Dart 2 language features
Key takeaways
Dart 2 empowers developers with extensions, mixins, and generics, offering more flexible ways to extend libraries, share behaviors, and ensure type safety across reusable code components.
With enums for fixed states and robust handling of asynchronous tasks using Future and Stream classes combined with async and await, Dart 2 enables precise state control and efficient task management.
Dart is an object-oriented, class-based, simple, and clean language. Google’s popular mobile framework, Flutter, uses Dart to implement high-quality native applications. In 2018, Dart 2 was released with all sorts of new features that make it much safer, more expressive, and more usable.
This update modified the basics of the Dart language, libraries, build systems, and web development tools. Today, we’ll introduce you to the most important new language features of Dart 2 to take your web applications to the next level.
How to use Dart 2 extensions for enhanced code flexibility#
Dart extensions were officially released with Dart 2.7. The extensions feature adds functionality to existing Dart libraries. The extensions feature is useful when adding utility method(s) in a third-party library or a core class.
With the first version of Dart, adding methods in those classes was not always practical. Extensions help by implicitly extending the type, unlike the old style, where the object was explicitly passed as an argument to static methods.
Extensions are defined inside one block of code that begins with extension and contains a name for the extension, the on keyword, and the data type. Dart supports three types of extensions that you’ll look into in more detail below:
extension<T> on List<T> {
//extension methods
//extension operators
//extension properties
}
Extension methods#
Extension methods allow you to add new members to existing types. You might already be using extension methods. For example, using code completion in an IDE will suggest an extension method. Extension methods are located in libraries.
Note: You use them by importing the right library and using them like an ordinary method.
Let’s see an example to understand extension methods. Below, we will write an extension method for the List data type. The extension method priceList() returns the price listing in the same condition. This method demonstrates how extensions implicitly extend the type using this.
The output will be:
Price listing: [1,1.99,4]
Extension operators#
Dart also provides support for operators. Below, we will define an operator extension for the ^ operator. We assume this operator increases the price by n times, where n is the passed argument.
The operator keyword declares an extension operator, and the operator sign follows it. In the following example, this list is iterated to multiply each item by n, and then the updated list is returned.
extension<T> on List<T> {
//Extension Operator: Hike up the price by n
List<num> operator ^(int n) =>
this.map((item) => num.parse("${item}") * n).toList();
}
The operator ^ is applied to the prices list. The operator ^ multiplies each item in prices by 3 and returns. The updated list is then printed using the print() method.
void main() {
//List of prices
List prices = [1, 1.99, 4];
print("\nPrice listing after hiking up prices 3x of the original value");
//argument is passed after the operator sign
print(prices ^ 3);
}
This gives us the output below:
Price listing after hiking up prices 3x of the original value
[3, 5.97, 12]
Extension property#
Dart also provides support for properties. In the extension below, we add a property that returns the total number of printed price labels needed for a price listing. Let’s assume we want to print 3 labels for each price amount in the list [1, 1.99, 4].
An extension property definition has three parts: the type of data to be returned by the property, the get keyword, and the property name. For example:
<return_type> get <property_name> => //implementation
The number of labels is type int, and the property name is labelCount. We need 3 labels for each item, so we need three times the total size of the list. We can calculate the number of total labels as length * 3. The extension has implicit access to the length property.
extension<T> on List<T> {
//Extension Property: 3 printed labels for each price.
int get labelCount => length * 3;
}
The property labelCount is called on the price listing prices.
void main() {
//List of prices
List prices = [1, 1.99, 4];
print("\nNumber of Printed Labels:");
print(prices.labelCount);
}
This gives us the following output:
Number of Printed Labels:
9
Dart enums#
Enumerated types (or enums) were added with the release of Dart 1.8. Enums act like a class that represents a fixed number of constant values. For example, you could have an app fetching data from a remote server. The app shows one of the following statuses:
done: The app received the response successfully.waiting: The app is waiting for the response.error: The app received an error from the server.
The above responses can be declared using the enum keyword:
enum Status {
done,
waiting,
error, //This comma is optional
}
Let’s understand this better with an example. Pretend we have a weather application. We need to represent the states of the weather: sunny, cloudy, and rainy.
We could represent these states with a const keyword.
const SUNNY = 'Sunny';
const CLOUDY = 'Cloudy';
const RAINY = 'Rainy';
Or, we could use enumerated types with the enum keyword.
enum Weather {
sunny,
cloudy,
rainy,
}
Switch block with enums#
We can use the switch block for enums. It requires case blocks for all members of our enum class and a default block if a case-block implementation is missing; otherwise, you get a compilation error. Take a look at the example below:
Note:
switchblock implementations can be done for constants and enums. However, enums are preferred when you do not want to lose an opportunity to handle a particular case.
#
Keep the learning going!
Learn to develop web applications using Dart without scrubbing through videos or documentation. Educative’s text-based courses are easy to skim and feature live coding environments, making learning quick and efficient.
Dart mixins#
Mixins allow our Dart code to be reusable across separate classes. Reusing code from classes that share common behaviors is far more efficient. A mixin class contains methods other classes use, but it is not their parent. Therefore, unlike interfaces and abstract classes, we can use the code from a class without inheriting it.
Mixins are declared using the mixin keyword:
mixin SharedBehavior {
}
The syntax of mixins is simple. Below, B is the parent class to A. C is the mixin with methods that B can implement.
class A extends B with C {
//Implement methods from B & C
}
Mixins example#
Let’s understand this better with an example. Say we have different people with different occupations: artists, engineers, doctors, and athletes. We can assume that the four types of people share common behaviors (like sketching, reading, exercise, boxing, etc.) in addition to inheriting the class Person.
In other words, each type of person extends the
Personclass and one or more shared behaviors.
Overlapping common behaviors like these can be extracted into mixins. We will create mixins for these behaviors. For example, the Sketching mixin defines the common sketch() method, which takes a message parameter.
mixin Sketching {
sketch(String message) {
print(message);
}
}
Let’s see our example in code. Below, in the main() method, each class’s object is created, and method(s) are called for their shared behavior, which is implemented using mixins.
Dart generics#
Generics are used to apply stronger type checks at compile-time. They also enforce type safety. Generics help write reusable classes and methods/functions for different data types. Generics in Dart are similar to Java generics or C++ templates.
Definition: Type safety is a programming concept allowing a memory block to contain only one data type.
Dart’s collection can hold different data types in one collection, but the program can crash if a particular data type is not handled appropriately. Generics can solve this problem by enforcing one data type in the collection.
Let’s learn how to declare type safe collections. To ensure type safety, the angular brackets <> with data type enclosed declare the data type collection.
CollectionType <dataType> identifier = CollectionType <dataType>();
Generics are parameterized, using variable notations to restrict the data type. We commonly represent these type variables with single-letter names, such as:
- E represents the element type in a collection, i.e.,
List. - R represents the return type of a function or method.
- K represents the key type in associative collections, i.e.,
Map. - V represents the value type in associative collections, i.e.,
Map.
Note: We can also represent generics with descriptive names like
ProductorInventoryTake a look at an example of the single-letter name generics implementation.
Asynchronicity in Dart#
Asynchronicity allows multiple things to happen at the same time. In Dart, asynchronous operations can perform time-consuming operations, allowing their processing to finish later.
Dart provides two ways to handle events/requests asynchronously. Dart’s dart:async library supports asynchronous programming with Future and Stream classes.
- Future objects represent the results of asynchronous operations. They are like promises for a future result.
- Stream objects provide a sequence of events that are either a value or an error.
Future objects#
Let’s explore future objects. Asynchronous operations results are returned as Future objects. The future object is represented as Future<T>, where T is the type of results returned. When we need the result of a completed Future, we can use either:
awaitandasync- The
FutureAPI
We use the await and async keywords together. The function expected to perform the expensive work will be marked with the keyword async. The keyword prefixes the expensive call await inside the function.
The program will suspend when await is called, when a function returns, or when it reaches the end of the function. Let’s see an example of async and await below:
Above, the Future keyword before the function makeDataCall() means this function will be executed asynchronously. Therefore, it will be suspended when it encounters await.
The makeDataCall() returns a Future of type void since there is nothing returned by the function. It calls the getExpansiveData() method with the keyword await, which returns the string I'm expansive data. The method makeDataCall() prints the results with the functionprint().
The makeDataCall() method returns a Future of type void since the function returns nothing. It calls the getExpansiveData() method with the keyword await, which returns the string I'm expansive data. The method makeDataCall() prints the results with the function print().
The
FutureAPI can also be used to execute asynchronous operations. In theFutureAPI, thethen()method registers a callback, which fires upon the completion ofFuture.There are two variants of the Future API.
Future<String>: Future returningStringdata type.Future<void>: Future returningvoid.
Null safety: A foundational change in Dart#
One of the most important changes since Dart 2’s release is sound null safety. Introduced in Dart 2.12 and now fully integrated into Dart 3, null safety eliminates an entire class of runtime errors by distinguishing between nullable and non-nullable types at compile time.
Here’s what you need to know:
Use ? to mark a type as nullable (String? name), and Dart will require you to handle null explicitly.
Use the ! operator to tell the compiler you’re sure a value isn’t null.
Use late to defer initialization when you know a variable will be assigned before use.
Migrating to null safety is now considered essential — and all modern Dart code, including Flutter apps, should use it.
Migration guide: Moving from Dart 2 to Dart 3#
If you’re maintaining older Dart projects, updating to Dart 3 may feel daunting — but the migration path is well supported.
Step 1: Enable null safety and migrate dependencies.
Step 2: Update your code to use enhanced enums, records, and class modifiers where appropriate.
Step 3: Adopt new best practices like pattern matching and extension types to simplify your codebase.
Including a brief migration guide helps developers understand how to modernize their projects without breaking changes.
Conclusion and next steps#
Congrats! You should now have a good understanding of what Dart 2 brings to the table. With Dart in our Flutter projects, we can make high-quality native applications, and these new features take your Dart skills to the next level. Dart 2 introduced safer, more expressive, and flexible features, enhancing the language’s usability for web and native applications. Key updates include extensions for adding functionality, mixins for code reusability, and robust support for generics, enums, and asynchronous programming.
But there is still more to learn about Dart 2 to use this language to its full potential. Your next learning steps are the following concepts:
- Callable classes
- Generator Functions
- Generic collections
- API and Streams in Dart
- OS Variables/
PlatformClass
To start with these intermediate Dart 2 concepts, check out Educative’s Developing Web Applications with Dart course. You will dive deep into Dart 2 language features with hands-on code. Gain confidence using everything from extensions to callable classes and beyond. By the end, you’ll be able to use this language in your own Flutter projects.
Happy learning!
Continue reading about mobile development and Flutter#
Frequently Asked Questions
What are the features of Dart?
What are the features of Dart?
What is the difference between enum and enum Dart?
What is the difference between enum and enum Dart?
How do Dart Generics work for type safe programming?
How do Dart Generics work for type safe programming?
Is Dart suitable for backend web development?
Is Dart suitable for backend web development?