Dart
Dart is a client-optimized language for fast apps on any platform. It is developed by Google and can be used to build mobile, desktop, server, and web applications.
All examples are based on Dart SDK version: 3.2.5.
Resources:
Getting start
To run Dart code, you need to have Dart SDK installed on your machine. You can download it from the official website.
//playground.dart
void main() {
print('Hello, World!');
}
//running: dart playground.dart
Using docker:
docker run --rm -v .:/app dart dart app/playground.dart
Topics
- Variables
- Types
- Operators
- Operator overloading
- Strings
- Null safety
- Collections
- Conditional statements
- Loops
- Functions
- Classes
- Class inheritance
- Abstract class
- Implicit interfaces
- Object extensions
- Mixins
- Enum
- Async
- Error handling
- Generics
- Streams
- Generators
- Import, Export
- Isolates
- Keywords
Variables
//var: mutable variable
var name = 'Dart';
//final: immutable variable, constant at runtime
final age = 25;
//const: immutable variable, constant at compile time
const pi = 3.14;
//dynamic: dynamic type
dynamic dynamicVar = 'dynamic';
//typed variable
String typedVar = 'typed';
Primitive Types
//int
int age = 25;
//double
double pi = 3.14;
//String
String name = 'Dart';
//bool
bool isDartCool = true;
//function
Function fn = (arg) {
print('a simple function');
};
Operators
//Arithmetic
var sum = 1 + 2;
var sub = 2 - 1;
var mul = 2 * 2;
var div = 4 / 2;
var mod = 5 % 2;
//Equality and relational
var isEqual = 1 == 1;
var isNotEqual = 1 != 2;
var isGreater = 2 > 1;
var isLess = 1 < 2;
var isGreaterOrEqual = 2 >= 2;
//Logical
var and = true && true;
var or = true || false;
var not = !true;
//Assignment
var a = 1;
a += 1;
a -= 1;
a *= 2;
a /= 2;
a %= 2;
//Conditional
var isTrue = true;
var result = isTrue ? 'yes' : 'no';
//Type test
var isString = 'Dart' is String;
var isNotString = 'Dart' is! String;
//Bitwise
var andBit = 0 & 1;
var orBit = 0 | 1;
var xorBit = 0 ^ 1;
var shiftRight = 1 >> 1;
var shiftLeft = 1 << 1;
//Null-aware
var value;
var result = value ?? 'default';
Cascade operator is used to perform a sequence of operations on the same object.
//cascade operator
var list = [1, 2, 3];
list
..add(4)
..remove(2)
..insert(1, 5)
..removeAt(0)
..clear();
Operator overloading
Operators can be overloaded in Dart. For example, you can define the +
operator for your custom class.
class Vector {
final int x, y;
Vector(this.x, this.y);
//overloading +, -, *
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
Vector operator *(int scalar) => Vector(x * scalar, y * scalar);
}
void main() {
final v1 = Vector(1, 2);
final v2 = Vector(2, 3);
//using overloaded operators
final sum = v1 + v2;
final sub = v1 - v2;
final mul = v1 * 2;
print('sum: $sum, sub: $sub, mul: $mul');
}
Strings
//single line
var singleLine = 'Dart is a client-optimized language';
//multi-line
var multiLine = '''
Dart is a client-optimized language
for fast apps on any platform.
''';
//string interpolation
var name = 'Dart';
var age = 25;
var result = 'name: $name, age: $age';
//raw string
var rawString = r'raw string \n';
Null safety
Null safety is a feature that helps you avoid null reference exceptions. It is introduced in Dart 2.12.0.
//non-nullable variable
String name = 'Dart';
//nullable variable
String? nullableName = null;
//null-aware operator
var result = nullableName?.length;
//null assertion operator
var length = nullableName!.length;
//late variable
late String lateName;
//late initialization
void main() {
lateName = 'Dart';
print(lateName);
}
//late initialization with null
late String? lateNullableName;
void main() {
lateNullableName = null;
print(lateNullableName);
}
//late initialization with null safety
late String lateName;
void main() {
lateName = 'Dart';
print(lateName);
}
Collections
//List: is an ordered collection of objects
var list = [1, 2, 3];
var typedList = <int>[1, 2, 3];
//some methods
list.add(4);
list.remove(2);
list.insert(1, 5);
list.removeAt(0);
list.clear();
list.forEach((item) => print(item));
//Set: is an unordered collection of unique objects
var set = {1, 2, 3};
var typedSet = <int>{1, 2, 3};
//some methods
set.add(4);
set.remove(2);
set.clear();
set.forEach((item) => print(item));
//Map: is a collection of key-value pairs
var map = {'name': 'Dart', 'age': 25};
var typedMap = <String, int>{'age': 25};
//some methods
map['name'] = 'Dart';
map.remove('age');
map.clear();
map.forEach((key, value) => print('$key: $value'));
Records are an anonymous, immutable, aggregate type. Like other collection types, they let you bundle multiple objects into a single object. Unlike other collection types, records are fixed-sized, heterogeneous, and typed.
//simple record
(String, int) r1 = ('John Doe', 10);
//named record
({String name, int age}) r2 = (name: 'John Doe', age: 10);
//function that receives a record
void rf1(({String name, int age}) r) {
print("${r.name} ${r.age}");
}
rf1((name: 'John Doe', age: 10));
//function that returns a record
({String name, int age}) rf2() {
return (name: 'John Doe', age: 10);
}
//destruc a record with type
(String, int) r5 = ('John Doe', 10);
var (name1, age1) = r5;
print("$name1 $age1");
//destruc a named record
({String name, int age}) r6 = (name: 'John Doe', age: 10);
var (:name, :age) = r6;
print("$name $age");
//generic and record example
({T name, int age}) r7<T>(T name) {
return (name: name, age: 10);
}
Conditional Statements
//if-else
var isTrue = true;
if (isTrue) {
print('yes');
} else {
print('no');
}
//if-else if
var option = 1;
if (option == 1) {
print('option 1');
} else if (option == 2) {
print('option 2');
} else {
print('default option');
}
//switch
var option = 1;
switch (option) {
case 1:
print('option 1');
break;
case 2:
print('option 2');
break;
default:
print('default option');
}
//ternary
var result = isTrue ? 'yes' : 'no';
Loops
//for
for (var i = 0; i < 3; i++) {
print(i);
}
//for-in
var list = [1, 2, 3];
for (var item in list) {
print(item);
}
//while
var i = 0;
while (i < 3) {
print(i);
i++;
}
//do-while
var j = 0;
do {
print(j);
j++;
} while (j < 3);
Functions
//function
void printName(String name) {
print(name);
}
//function with return type
int sum(int a, int b) {
return a + b;
}
//function with optional parameters
void printOptional(String name, [int? age]) {
print('name: $name, age: $age');
}
//function with named parameters
void printNamed({required String name, int? age}) {
print('name: $name, age: $age');
}
//function with default parameters
void printDefault({String name = 'Dart', int age = 25}) {
print('name: $name, age: $age');
}
//function as a parameter
void execute(Function fn) {
fn();
}
void main() {
printName('Dart');
print(sum(1, 2));
printOptional('Dart');
printNamed(name: 'Dart');
printDefault();
execute(() {
print('a simple function');
});
}
Classes
//simple class
class Point1 {
var x;
var y;
Point1(num x, num y) {
this.x = x;
this.y = y;
}
}
//short syntax
class Point2 {
var x;
var y;
Point2(this.x, this.y);
}
//named constructor
class Point3 {
var x, y;
Point3.string(String this.x, String this.y);
Point3.integer(int this.x, int this.y);
Point3.number(num this.x, int this.y);
Point3.origin() {
x = 0;
y = 0;
}
Point3.middle() {
x = 10;
y = 10;
}
}
//initializer and validate
class Person {
var name, age;
People(String this.name, int this.age) : assert(age >= 18) {
print('Person class initilized');
}
}
//redirecting constructor
class Cords {
num x, y;
Cords(this.x, this.y);
//Delegate to the main constructor.
Cords.origin(num x) : this(x, 0);
}
//constant constructor
class ImmutablePoint {
final num x, y;
const ImmutablePoint(this.x, this.y);
//immutable
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
//methods
class Foo {
int first() {
return 1;
}
String second() {
return '2';
}
}
//getters and setters
class Rectangle {
/*
you can create additional properties
by implementing setters and getters
*/
num get left => 100;
set left(num value) => left = value;
num get right => 100;
set right(num value) => right = value;
num get bottom => 10;
set bottom(num value) => bottom = bottom;
num get top => 10;
set top(num value) => value = value;
}
Static members are shared among all instances of a class. You can access static members using the class name.
class Circle {
//property
num radius;
//static property
static const pi = 3.14;
Circle(this.radius);
//method
num calculateArea() {
return radius * radius * pi;
}
//static method
static num calculateArea(num radius) {
return radius * radius * pi;
}
}
The sealed modifier prevents a class from being extended or implemented outside its own library. Sealed classes are implicitly abstract.
sealed class Colors {
static String red = "red";
static String green = "green";
static String blue = "blue";
}
Class inheritance
class Animal {
var name;
Animal(this.name);
void speak() {
print('Animal speaks');
}
}
class Dog extends Animal {
Dog(String name) : super(name);
//overriding method
@override
void speak() {
print('Dog barks');
}
}
Abstract class
abstract class Animal {
void speak();
}
class Dog extends
Animal {
@override
void speak() {
print('Dog barks');
}
}
Implicit interfaces
In dart, all classes have an implicit interface. This means that all classes can be extended and implemented.
class Animal {
void speak() {
print('Animal speaks');
}
}
class Dog implements Animal {
@override
void speak() {
print('Dog barks');
}
}
Object extensions
Dart 2.7.0 introduces extension methods, which allow you to add new functionality to existing classes.
//extension, add capitalize method to String class
extension StringExtension on String {
String capitalize() {
return '${this[0].toUpperCase()}${this.substring(1)}';
}
}
void main() {
var name = 'dart';
print(name.capitalize());
}
Mixins
Mixins are a way to reuse a class’s code in multiple class hierarchies.
//mixin example
mixin Swimmer {
void swim() => print('Swimming');
}
//mixin with properties
mixin Flyer {
int age = 0;
void fly() => print('Flying');
}
//mixin with methods
mixin Runner {
void run() => print('Running');
void runFor(int distance) => print('Running for $distance meters');
}
//mixin with abstract methods
mixin Jumper {
void jump() => print('Jumping');
void jumpOver(int distance);
}
//mixin that extends another mixin
mixin SuperJumper on Jumper {
void superJump() => print('Super Jumping');
}
//class that use mixins
class Animal with Swimmer, Flyer, Runner, Jumper, SuperJumper {
String name;
Animal(this.name);
void eat() => print('$name is eating');
void jumpOver(int distance) => print('$name is jumping over $distance meters');
}
void main() async {
final flipper = Animal('Flipper');
flipper.eat();
flipper.swim();
flipper.fly();
flipper.run();
flipper.runFor(100);
flipper.jump();
flipper.jumpOver(10);
flipper.superJump();
print(flipper.age);
flipper.age = 10;
print(flipper.age);
}
Enum
//enum
enum Days {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
//enum with values
enum Color {
red('red'),
green('green'),
blue('blue');
const Color(this.name);
final String name;
}
void main() {
printColor(Color.red); //red
printColor(Color.green); //green
printColor(Color.blue); //blue
}
Async
Dart supports asynchronous programming using Future
and async
and await
keywords.
Future: represents a potential value or error that will be available at some time in the future.
//async function
Future<void> fetchData() async {
var data = await fetch('https://api.example.com/data');
print(data);
}
//async function with error handling
Future<void> fetchData() async {
try {
var data = await fetch('https://api.example.com/data');
print(data);
} catch (e) {
print(e);
}
}
//async function with multiple futures
Future<void> fetchData() async {
var data1 = fetch('https://api.example.com/data1');
var data2 = fetch('https://api.example.com/data2');
var data3 = fetch('https://api.example.com/data3');
var results = await Future.wait([data1, data2, data3]);
print(results);
}
Resolving with .then()
and .catchError()
fetch('https://api.example.com/data')
.then((data) => print(data))
.catchError((e) => print(e));
Completer
Completers in Dart are objects that allow you to create and complete Futures manually
import 'dart:async';
Future<void> delayedMessage(String message, Duration duration) async {
var completer = Completer<void>();
Timer(duration, () {
completer.complete();
});
await completer.future;
print(message);
}
void main() async {
await delayedMessage('Hello, World!', Duration(seconds: 3));
}
Error handling
//throwing an error
void throwError() {
throw Exception('An error occurred');
}
//catching an error
void catchError() {
try {
throwError();
} catch (e) {
print(e);
}
}
//typed error
void throwError() {
throw FormatException('An error occurred');
}
//catching a typed error
void catchError() {
try {
throwError();
} on FormatException catch (e) {
print(e);
}
}
//custom error
class CustomError implements Exception {
final String message;
CustomError(this.message);
}
//throwing a custom error
void throwError() {
throw CustomError('An error occurred');
}
//catching a custom error
void catchError() {
try {
throwError();
} on CustomError catch (e) {
print(e.message);
}
}
Generics
Generics are a way to make your code more reusable and type-safe. You can use generics with classes, functions, constraints, mixins, extensions, and more.
//generic class
class Box<T> {
final T value;
Box(this.value);
}
//generic function
T identity<T>(T value) {
return value;
}
//generic constraint
T first<T extends List>(T list) {
return list[0];
}
//generic mixin
mixin Serializer<T> {
String toJson() {
return json.encode(this);
}
}
//generic extension
extension ListExtension<T> on List<T> {
T first() {
return this[0];
}
}
//using generic
void main() {
var box = Box<String>('Dart');
print(box.value);
var result = identity<String>('Dart');
print(result);
var firstItem = first<String>(['Dart', 'Flutter']);
print(firstItem);
var list = [1, 2, 3];
print(list.first());
}
Streams
Streams are a way to handle asynchronous data. You can use streams to handle data that is being produced over time.
//simple stream
var stm = Stream.fromIterable([1, 2, 3]);
stm.listen((event) {
print(event);
});
//stream from future
var stm = Stream.fromFuture(Future.value('Dart'));
stm.listen((event) {
print(event);
});
//stream from futures
var stm = Stream.fromFutures([
Future.value(1),
Future.value(2),
Future.value(3)
]);
stm.listen((event) {
print(event);
});
//transforming stream
var stm = Stream.fromIterable([1, 2, 3]);
stm.map((event) => event * 2).listen((event) {
print(event);
});
//periodic stream
var stm = Stream.periodic(Duration(seconds: 1), (event) => event);
stm.listen((event) {
print(event);
});
//broadcast stream, is a stream that allows multiple listeners
var stm = Stream
.periodic(Duration(seconds: 1), (event) => event)
.asBroadcastStream();
stm.listen((event) {
print('listener 1: $event');
});
//stream controller
var controller = StreamController<int>();
controller.stream.listen((event) {
print(event);
});
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
Generators
Generators are a way to create iterable collections of data. You can use generators to create lazy sequences of data that are produced on demand. Async generators are a way to create a stream of data.
//sync generator
Iterable<int> count(int n) sync* {
for (var i = 0; i < n; i++) {
yield i;
}
}
//async generator
Stream<int> count(int n) async* {
for (var i = 0; i < n; i++) {
yield i;
}
}
//using generator
void main() {
var result = count(3);
result.forEach((element) {
print(element);
});
}
Import, Export
//importing a library
import 'dart:math';
//importing a library with a prefix
import 'dart:math' as math;
//importing only specific parts of a library
import 'dart:math' show min, max;
//importing everything except specific parts of a library
import 'dart:math' hide min, max;
Importing a file from the same directory:
//calc.dart
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
//private memebers are prefixed with an underscore.
int _mul(int a, int b) {
return a * b;
}
//main.dart
import 'calc.dart';
void main() {
print(add(1, 2));
print(sub(2, 1));
}
In Dart, the export keyword is used to share library code with other libraries. It allows a library to re-export any of its imported libraries, making the re-exported libraries’ APIs available to the library’s consumers.
export '/components/components.dart';
export '/domain/domain.dart';
export '/dto/dto.dart';
export '/data/data.dart';
export '/core/core.dart';
export '/notifiers/notifiers.dart';
export '/views/views.dart';
export '/style/text_style.dart';
Isolates
Isolates are a way to run Dart code in a separate thread. You can use isolates to run code in parallel, without sharing memory.
import 'dart:isolate';
void main() async {
var result = await Isolate.run(() {
return "hello from isolate";
});
print(result);
print("hello world");
}
Keywords
Built-in Types
Keyword | Description |
---|---|
bool | Boolean type representing true or false. |
double | Represents a 64-bit floating-point number. |
int | Represents a 64-bit integer. |
num | Represents either an integer or a double. |
String | Represents a sequence of characters. |
Keywords Used in Declarations
Keyword | Description |
---|---|
abstract | Indicates an abstract class or method. |
class | Declares a class. |
enum | Declares an enumeration. |
extends | Indicates that a class is inheriting from another class. |
implements | Indicates that a class is implementing an interface. |
import | Imports a library. |
library | Declares a library. |
part | Declares that a file is part of a library. |
typedef | Declares a function type alias. |
Keywords Used in Control Flow
Keyword | Description |
---|---|
assert | Used for debugging; checks if a condition is true. |
break | Exits a loop or switch statement. |
case | Defines a case clause in a switch statement. |
catch | Catches exceptions. |
continue | Skips the rest of the current loop iteration. |
default | Specifies the default case in a switch statement. |
do | Starts a do-while loop. |
else | Specifies the alternative block of code in an if-else statement. |
finally | Declares a block of code that will always be executed. |
for | Starts a for loop. |
if | Starts a conditional statement. |
in | Used in for-in loops. |
rethrow | Re-throws the current exception. |
return | Returns a value from a function. |
switch | Starts a switch statement. |
throw | Throws an exception. |
try | Starts a try block to handle exceptions. |
while | Starts a while loop. |
yield | Pauses the generator function and returns a value. |
Contextual Keywords
Keyword | Description |
---|---|
as | Used for type casting. |
covariant | Specifies that a parameter can have a more specific type. |
deferred | Indicates that a library is loaded lazily. |
dynamic | Declares a variable without specifying its type. |
export | Exports a library. |
external | Declares that a function or variable is implemented externally. |
factory | Declares a factory constructor. |
get | Defines a getter method. |
implements | Indicates that a class is implementing an interface. |
late | Indicates that a variable will be initialized later. |
mixin | Declares a mixin. |
on | Specifies the superclass constraints for a mixin. |
operator | Declares an operator method. |
required | Specifies that a named parameter is required. |
set | Defines a setter method. |
static | Declares a static member of a class. |
sync | Indicates that a generator function is synchronous. |
Keywords Used in Asynchronous Programming
Keyword | Description |
---|---|
async | Indicates that a function is asynchronous. |
await | Waits for an asynchronous operation to complete. |
Keywords for Null Safety
Keyword | Description |
---|---|
late | Indicates that a variable will be initialized later. |
Miscellaneous Keywords
Keyword | Description |
---|---|
await | Waits for an asynchronous operation to complete. |
const | Declares a compile-time constant. |
final | Declares a variable that can only be set once. |
is | Checks the type of an object. |
new | Creates a new instance of an object. |
super | Refers to the superclass. |
this | Refers to the current instance. |
void | Specifies that a function does not return a value. |
Reserved Words for Future Use
Keyword | Description |
---|---|
assert | Used for debugging; checks if a condition is true. |
break | Exits a loop or switch statement. |
case | Defines a case clause in a switch statement. |
catch | Catches exceptions. |
class | Declares a class. |
const | Declares a compile-time constant. |
continue | Skips the rest of the current loop iteration. |
default | Specifies the default case in a switch statement. |
do | Starts a do-while loop. |
else | Specifies the alternative block of code in an if-else statement. |
enum | Declares an enumeration. |
extends | Indicates that a class is inheriting from another class. |
false | Boolean literal for false. |
final | Declares a variable that can only be set once. |
finally | Declares a block of code that will always be executed. |
for | Starts a for loop. |
if | Starts a conditional statement. |
in | Used in for-in loops. |
is | Checks the type of an object. |
new | Creates a new instance of an object. |
null | Represents a null value. |
rethrow | Re-throws the current exception. |
return | Returns a value from a function. |
super | Refers to the superclass. |
switch | Starts a switch statement. |
this | Refers to the current instance. |
throw | Throws an exception. |
true | Boolean literal for true. |
try | Starts a try block to handle exceptions. |
var | Declares a variable. |
void | Specifies that a function does not return a value. |
while | Starts a while loop. |
with | Indicates that a class uses a mixin. |