loading...
Cover image for Introducing Dart 2 language features: mixins, enums, and more
Educative

Introducing Dart 2 language features: mixins, enums, and more

amandaeducative profile image Amanda Fawcett Originally published at educative.io ・11 min read

Dart is an object-oriented, class-based language used with Flutter. Dart's recent update introduced new features like enums, mixins, extensions, and more. Today, we explore the most important Dart 2 updates with code examples.


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.

Flutter and Dart are a powerful combination, so it’s important to understand all that Dart 2 can do and get the full potential of the language. Today, we’ll introduce you to the most important new language features of Dart 2 to take your web applications to the next level.

To be most successful with this article, a basic knowledge of Flutter and Dart is recommended. Check out our article Flutter Tutorial: the beginner's guide to cross-platform apps before continuing here.

Today, we will go over:

Alt Text

Dart Extensions

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
}
Enter fullscreen mode Exit fullscreen mode

Extension methods

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 method
extension<T> on List<T> {
  //Extension Method demonstration
  List<T> priceList() => this.map((item) => item).toList();

}

void main() {
  //List of prices
  List prices = [1, 1.99, 4];

  print("Price listing:");

  //priceList() is being called on `prices` list 
  //and returns the list of prices
  print(prices.priceList());
}
Enter fullscreen mode Exit fullscreen mode

Output is "Price listing: [1, 1.99, 4]"

Extension operators

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(); 
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

This gives us the output below:

Price listing after hiking up prices 3x of the original value
[3, 5.97, 12]
Enter fullscreen mode Exit fullscreen mode

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 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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

This gives us the following output:

Number of Printed Labels:
9
Enter fullscreen mode Exit fullscreen mode

Dart Enums

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:

  • 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
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

Or, we could use enumerated types with the enum keyword.

enum Weather {
  sunny,
  cloudy,
  rainy,
}
Enter fullscreen mode Exit fullscreen mode

Switch block with Enums

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 information
enum 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 available
  switch (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}");
  }
}
Enter fullscreen mode Exit fullscreen mode

Keep the learning going.

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. By the end of this course, you’ll be able to use this language in your own Flutter projects.

Developing Web Applications with Dart


Dart Mixins

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 {
 }
Enter fullscreen mode Exit fullscreen mode

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

}
Enter fullscreen mode Exit fullscreen mode

Mixins example

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 class
abstract class Person {
  int age;
  int name;

  eat() {}
  sleep() {}
}

//Artist class
class Artist extends Person with Sketching {
  sketchLandscape() {
    sketch("Making landscapes sketches");
  }
}

//Engineer class
class Engineer extends Person with Sketching, Reading {
  sketchBuildings() {
    sketch("Sketching engineering drawings");
  }

  readResearchPaper() {
    String topic = "Building Construction";
    dailyReading(topic);
  }
}

//Doctor class
class Doctor extends Person with Reading, Exercise {
  readReports() {
    String topic = "flu";
    dailyReading(topic);
  }

  workout() {
    running(1);
    weightTraining(10);
  }
}

//Athlete class
class Athlete extends Person with Exercise {
  generalRoutine() {
    running(2);
    weightTraining(20);
  }
}

//Boxer class
class Boxer extends Athlete with Boxing {
  punchPractice() {
    punch(100);
  }

  routineExercise() {
    running(4);
    weightTraining(40);
  }
}

//Mixins

//Sketching mixin
mixin Sketching {
  sketch(String message) {
    print(message);
  }
}

//Reading mixin
mixin Reading {
  dailyReading(String topic) {
    print("Daily reading on ${topic}");
  }
}

//Exercise mixin
mixin Exercise {
  running(int mile) {
    print("Daily run of ${mile} mile(s)");
  }

  weightTraining(int weights) {
    print("Lifting ${weights} lbs");
  }
}

//Boxing
mixin 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();
}
Enter fullscreen mode Exit fullscreen mode

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 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>();
Enter fullscreen mode Exit fullscreen mode

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:

  • 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 Product or Inventory
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 product
class Product {
  final int id;
  final double price;
  final String title;
  Product(this.id, this.price, this.title);

  @override
  String toString() {
    return "Price of ${this.title} is \$${this.price}";
  }
}

//A class for product's inventory
class Inventory {
  final int amount;

  Inventory(this.amount);

  @override
  String toString() {
    return "Inventory amount: $amount";
  }
}

//Custom type variables- Single letter
class 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 letter
void main() {
  Product milk = Product(1, 5.99, "Milk");
  Product bread = Product(2, 4.50, "Bread");

  //Using single letter names for Generics
  Store<Product, Inventory> store1 = Store<Product, Inventory>();
  store1.updateInventory(milk, Inventory(20));
  store1.updateInventory(bread, Inventory(15));
  store1.printProducts();
}
Enter fullscreen mode Exit fullscreen mode

Output:
Product: Price of Bread is $4.5, Inventory amount: 15
Product: Price of Milk is $5.99, Inventory amount: 20


Asynchrony in Dart

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.

  • Future objects: represent the results of asynchronous operations. This is like a promise for a result to arrive in the future.
  • Stream objects: provides 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:

  • await and async
  • The Future API

We 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-world
String 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 function
void main() {
  makeDataCall();
}
Enter fullscreen mode Exit fullscreen mode

Output: I'm expansive data

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 the Future API, the then() method registers a callback, which fires upon the completion of Future.

There are two variants of the Future API.

  • Future<String>: Future returning String data type .
  • Future<void>: Future returning void.

What to learn next

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:

  • Callable Classes
  • Generator Functions
  • Dart Libraries
  • Generic Collections
  • API and Streams in Dart
  • OS Variables/Platform Class
  • Encryption in Dart (encrypt package)

To 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. You can even earn a certificate and add Dart skills to your resume!

Happy learning!

Continue reading about mobile development and Flutter

Discussion

pic
Editor guide
Collapse
richardhaven profile image
No Clever Code

To be blunt, your examples do not show the actual power of Mixins. What you show could be better implemented by adding behaviours to the Person class and its various descendants. Perhaps showing that dogs and persons can share some common behaviours, but cannot descend from the same base class

As for Generics, do you think that using single-letter variable names, specified by convention, is a good idea? This is not Rails. We strive towards giving things good names because it helps. Let's not regress when specifying generics

Your Future examples have almost exactly the same behaviour as a regular call: wait until the call returns

Cheers