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 system, 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.
Today, we will go over:
Dive deep into the features of Dart 2. Gain a strong understanding of new Dart principles and be ready to use this language in any Flutter project.
Dart extensions were officially released with Dart 2.7. The extensions feature adds functionality to existing Dart libraries. The extensions feature is useful when you want to add utility method(s) in a third-party library or a core class.
With the first version of Dart, it was not always practical to add methods in those classes. Extensions help by implicitly extending the type as opposed to 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 we will go over in more detail below:
extension<T> on List<T> {
//extension methods
//extension operators
//extension properties
}
Extension methods allow you to add new members to existing types. You might already be using extension methods. For example, when using code completion in an IDE, it will suggest an extension method. Extension methods are located in libraries.
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
.
//Local extension methodextension<T> on List<T> {//Extension Method demonstrationList<T> priceList() => this.map((item) => item).toList();}void main() {//List of pricesList prices = [1, 1.99, 4];print("Price listing:");//priceList() is being called on `prices` list//and returns the list of pricesprint(prices.priceList());}
Dart provides support for operators as well. Below, we will define an operator extension for the ^
operator. We assume this operator increases the price by the n
times, where n
is the passed argument.
The operator
keyword declares an extension operator. It is followed by the operator sign. In the following example, the this
list is iterated over 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]
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 name of the property. For example:
<return_type> get <property_name> => //implementation
The number of labels is of 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
Enumerated Types (a.k.a. Enums) were added with the release of Dart 1.8. Enums act like a class that represent a fixed number of constant values. For example, you could have an app that fetches data from a remote server. The app shows one of the following statuses:
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 and pretend we have a weather application. We need to represent 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,
}
We can use the switch
block for enums, and 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:
switch
block 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.
//Using Enums to display weather informationenum Weather {sunny,cloudy,rainy,}void main() {var weather = Weather.sunny;//Following code will complain about if all members are not present//Use default-block when all case-blocks are not availableswitch (weather) {case Weather.sunny:print("Sunny weather today!");break;case Weather.cloudy:print("Cloudy today!");break;case Weather.rainy:print("Rainy and gloomy weather.");break;default:print("Current weather:${weather}");}}
Learn how 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.
Mixins allow our Dart code to be reusable across separate classes. It is far more efficient to reuse code from classes that share common behaviors. A mixin class contains methods used by other classes, but it is not their parent. Therefore, we can use the code from a class without inheriting from it, unlike interfaces and abstract classes.
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
}
Let’s understand this better with an example. Say we have different people with different occupations: artist, engineer, doctor, and athlete. We can assume that the four types of people share a combination of common behaviors (like sketching, reading, exercise, boxing, etc.) in addition to inheriting the class Person
.
In other words, each type of person is extending the
Person
class 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. This method 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’ object is created and method(s) are called for their shared behavior which is implemented using Mixins.
//Person classabstract class Person {int age;int name;eat() {}sleep() {}}//Artist classclass Artist extends Person with Sketching {sketchLandscape() {sketch("Making landscapes sketches");}}//Engineer classclass Engineer extends Person with Sketching, Reading {sketchBuildings() {sketch("Sketching engineering drawings");}readResearchPaper() {String topic = "Building Construction";dailyReading(topic);}}//Doctor classclass Doctor extends Person with Reading, Exercise {readReports() {String topic = "flu";dailyReading(topic);}workout() {running(1);weightTraining(10);}}//Athlete classclass Athlete extends Person with Exercise {generalRoutine() {running(2);weightTraining(20);}}//Boxer classclass Boxer extends Athlete with Boxing {punchPractice() {punch(100);}routineExercise() {running(4);weightTraining(40);}}//Mixins//Sketching mixinmixin Sketching {sketch(String message) {print(message);}}//Reading mixinmixin Reading {dailyReading(String topic) {print("Daily reading on ${topic}");}}//Exercise mixinmixin Exercise {running(int mile) {print("Daily run of ${mile} mile(s)");}weightTraining(int weights) {print("Lifting ${weights} lbs");}}//Boxingmixin Boxing on Athlete {punch(int n) {print("Boxer practicing ${n} punches");}}void main() {print("Artist");Artist artist = Artist();artist.sketchLandscape();print("\nEngineer");Engineer engineer = Engineer();engineer.sketchBuildings();engineer.readResearchPaper();print("\nDoctor");Doctor doctor = Doctor();doctor.readReports();doctor.workout();print("\nBoxer");Boxer boxer = Boxer();boxer.punchPractice();boxer.routineExercise();}
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 that allows a memory block to contain only one type of data.
Dart’s collection can hold different data types in one collection, but this can lead to crashing the program if a particular data type is not handled appropriately. Generics can solve this problem by enforcing one data type to the collection.
Let’s learn how to declare type-safe collections. To ensure type safety, the angular brackets <>
with data type enclosed, declare the collection of the given data type.
CollectionType <dataType> identifier = CollectionType <dataType>();
Generics are parameterized, so they use type variable notations to restrict the type of data. We commonly represent these type variables with single letter names, such as:
List
Map
Map
Note: We can also represent generics with descriptive names like
Product
orInventory
Take a look at an example of the single-letter name generics implementation.
import 'dart:collection';//Demonstrating use of single letter for generics//A class for grocery productclass Product {final int id;final double price;final String title;Product(this.id, this.price, this.title);@overrideString toString() {return "Price of ${this.title} is \$${this.price}";}}//A class for product's inventoryclass Inventory {final int amount;Inventory(this.amount);@overrideString toString() {return "Inventory amount: $amount";}}//Custom type variables- Single letterclass Store<P, I> {final HashMap<P, I> catalog = HashMap<P, I>();List<P> get products => catalog.keys.toList();void updateInventory(P product, I inventory) {catalog[product] = inventory;}void printProducts() {catalog.keys.forEach((product) => print("Product: $product, " + catalog[product].toString()),);}}//Demonstrating single lettervoid main() {Product milk = Product(1, 5.99, "Milk");Product bread = Product(2, 4.50, "Bread");//Using single letter names for GenericsStore<Product, Inventory> store1 = Store<Product, Inventory>();store1.updateInventory(milk, Inventory(20));store1.updateInventory(bread, Inventory(15));store1.printProducts();}
Asynchronicity allows multiple things to happen at the same time. In Dart, asynchronous operations can perform time-consuming operations and allow for their processing to finish at a later time.
Dart provides two ways to handle events/requests asynchronously. Dart’s dart:async
library provides support for asynchronous programming with Future
and Stream
classes.
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:
await
and async
Future
APIWe use the await
and async
keywords together. The function that is expected to perform the expensive work will be marked with the keyword async
. The expensive call is prefixed by the keyword 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:
// Expensive function could be a function that takes// long time to process data and return results.// Assume this function takes long time to return in real-worldString getExpansiveData() {return "I'm expansive data";}// This is the asynchronous function that makes the expensive// data call and prints the results.Future<void> makeDataCall() async {var data = await getExpansiveData();print(data);}//----END----////Entry point functionvoid main() {makeDataCall();}
Above, the Future
keyword before the function makeDataCall()
means that 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
Future
API can also be used to execute asynchronous operations. In theFuture
API, thethen()
method registers a callback, which fires upon the completion ofFuture
.There are two variants of the Future API.
Future<String>
: Future returningString
data type .Future<void>
: Future returningvoid
.
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.
But there is still more to learn about Dart 2 to truly use this language to its full potential. Your next learning steps are the following concepts:
Platform
ClassTo get started with these intermediate Dart 2 concepts, check out Educative’s course Developing Web Applications with Dart. 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 use this language in your own Flutter projects.
Happy learning!
Free Resources