NexusCS

Dart

Languages
Quick reference for Dart - a client-optimized language for fast apps on any platform and the primary language for Flutter development.
dart
flutter
mobile
async
null-safety

Getting started

Introduction

Dart is a client-optimized language for fast apps on any platform. Primary language for Flutter development with sound null safety.

Installation

# macOS via Homebrew
brew install dart

# Windows via Chocolatey
choco install dart-sdk

# Linux via apt
sudo apt update
sudo apt install dart

Hello World

void main() {
  print('Hello, World!');
}

Variables

// Type inference
var name = 'Alice';
var age = 30;

// Explicit types
String city = 'New York';
int count = 42;
double price = 9.99;
bool isActive = true;

// Final (runtime constant)
final currentDate = DateTime.now();

// Const (compile-time constant)
const pi = 3.14159;

Null Safety

Nullable Types

// Non-nullable (default)
String name = 'Alice';
// name = null; // Error!

// Nullable with ?
String? nickname;
nickname = null; // OK

int? age;
age = 30; // OK

Null-Aware Operators

// ?. (null-aware access)
String? name;
int? length = name?.length;

// ?? (null-coalescing)
String msg = name ?? 'Guest';

// ??= (null-aware assignment)
String? value;
value ??= 'default';

// ! (null assertion)
String? text = 'Hello';
int length = text!.length; // Assert non-null

Late Variables

// Late initialization
late String description;

void init() {
  description = 'Ready';
}

// Late final
late final String config;

void setup() {
  config = loadConfig();
}

Functions

Function Syntax

// Named function
int add(int a, int b) {
  return a + b;
}

// Arrow function
int multiply(int a, int b) => a * b;

// Anonymous function
var divide = (int a, int b) {
  return a / b;
};

Optional Parameters

// Named parameters
void greet({String? name, int age = 0}) {
  print('Hello $name, age $age');
}
greet(name: 'Alice', age: 30);
greet(); // OK

// Required named parameters
void register({required String email}) {
  print('Email: $email');
}
register(email: 'user@example.com');

Positional Parameters

// Optional positional
String say(String msg, [String? from]) {
  if (from != null) {
    return '$msg from $from';
  }
  return msg;
}

say('Hello'); // OK
say('Hello', 'Alice'); // OK

Async/Await

Future Basics

// Async function
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Data loaded';
}

// Using await
void loadData() async {
  String data = await fetchData();
  print(data);
}

Error Handling

// Try-catch with async
Future<void> getData() async {
  try {
    var result = await fetchData();
    print(result);
  } catch (e) {
    print('Error: $e');
  } finally {
    print('Cleanup');
  }
}

// Multiple awaits
Future<void> loadAll() async {
  var user = await fetchUser();
  var posts = await fetchPosts(user.id);
  print(posts);
}

Parallel Execution

// Wait for multiple futures
Future<void> loadParallel() async {
  var results = await Future.wait([
    fetchUser(),
    fetchPosts(),
    fetchComments(),
  ]);
  print(results);
}

// First to complete
var result = await Future.any([
  fetchFromServer1(),
  fetchFromServer2(),
]);

Streams

Stream Basics

// Create stream
Stream<int> countStream() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

// Listen to stream
await for (var value in countStream()) {
  print(value);
}

Stream Methods

// Transform stream
var doubled = stream.map((x) => x * 2);

// Filter stream
var evens = stream.where((x) => x % 2 == 0);

// First value
var first = await stream.first;

// Listen with callback
stream.listen(
  (data) => print(data),
  onError: (e) => print('Error: $e'),
  onDone: () => print('Done'),
);

StreamController

// Create controller
var controller = StreamController<String>();

// Add data
controller.sink.add('Hello');
controller.sink.add('World');

// Listen
controller.stream.listen(print);

// Close when done
controller.close();

Collections

Lists

// List literal
var numbers = [1, 2, 3, 4, 5];

// Typed list
List<String> names = ['Alice', 'Bob'];

// List operations
names.add('Charlie');
names.remove('Bob');
var first = names[0];
var length = names.length;

// Spread operator
var all = [...names, 'Dave'];

// If/for in collections
var items = [
  'Home',
  if (loggedIn) 'Logout',
  for (var i in numbers) 'Item $i',
];

Sets

// Set literal
var tags = {'flutter', 'dart', 'mobile'};

// Typed set
Set<int> ids = {1, 2, 3};

// Set operations
tags.add('async');
tags.remove('mobile');
var contains = tags.contains('dart');

// Set methods
var union = tags.union({'web'});
var intersection = tags.intersection({'dart'});

Maps

// Map literal
var user = {
  'name': 'Alice',
  'age': 30,
  'active': true,
};

// Typed map
Map<String, int> scores = {
  'alice': 95,
  'bob': 87,
};

// Map operations
scores['charlie'] = 92;
var aliceScore = scores['alice'];
scores.remove('bob');

// Iteration
scores.forEach((key, value) {
  print('$key: $value');
});

Classes

Basic Class

class Person {
  String name;
  int age;

  // Constructor
  Person(this.name, this.age);

  // Named constructor
  Person.guest() : name = 'Guest', age = 0;

  // Method
  void introduce() {
    print('I am $name, $age years old');
  }
}

// Usage
var person = Person('Alice', 30);
var guest = Person.guest();

Getters and Setters

class Rectangle {
  double width;
  double height;

  Rectangle(this.width, this.height);

  // Getter
  double get area => width * height;

  // Setter
  set dimensions(List<double> dims) {
    width = dims[0];
    height = dims[1];
  }
}

var rect = Rectangle(10, 20);
print(rect.area); // 200
rect.dimensions = [15, 25];

Inheritance

class Animal {
  String name;
  Animal(this.name);

  void makeSound() {
    print('Some sound');
  }
}

class Dog extends Animal {
  Dog(String name) : super(name);

  
  void makeSound() {
    print('Woof!');
  }
}

var dog = Dog('Rex');
dog.makeSound(); // Woof!

Mixins

Mixin Definition

// Define mixin
mixin Flyable {
  void fly() {
    print('Flying!');
  }
}

mixin Swimmable {
  void swim() {
    print('Swimming!');
  }
}

// Use mixins
class Duck with Flyable, Swimmable {
  void quack() {
    print('Quack!');
  }
}

var duck = Duck();
duck.fly();
duck.swim();
duck.quack();

Mixin Constraints

class Animal {
  void breathe() => print('Breathing');
}

// Mixin requires Animal
mixin Walker on Animal {
  void walk() {
    breathe();
    print('Walking');
  }
}

class Dog extends Animal with Walker {}

Extensions

Extension Methods

// Extend existing class
extension StringExtension on String {
  String get capitalized {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }

  bool get isEmail {
    return contains('@') && contains('.');
  }
}

// Usage
var name = 'alice';
print(name.capitalized); // Alice
print('test@example.com'.isEmail); // true

Extension Types

// Extension on int
extension NumberExtension on int {
  int get doubled => this * 2;

  bool get isEven => this % 2 == 0;

  String times(String str) {
    return str * this;
  }
}

print(5.doubled); // 10
print(4.isEven); // true
print(3.times('Ha')); // HaHaHa

Isolates

Basic Isolate

import 'dart:isolate';

// Function to run in isolate
void heavyTask(SendPort sendPort) {
  var result = 0;
  for (int i = 0; i < 1000000; i++) {
    result += i;
  }
  sendPort.send(result);
}

// Spawn isolate
void main() async {
  var receivePort = ReceivePort();

  await Isolate.spawn(
    heavyTask,
    receivePort.sendPort,
  );

  var result = await receivePort.first;
  print('Result: $result');
}

Isolate Communication

import 'dart:isolate';

void isolateFunction(SendPort sendPort) {
  var receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);

  receivePort.listen((message) {
    if (message == 'ping') {
      sendPort.send('pong');
    }
  });
}

void main() async {
  var receivePort = ReceivePort();
  await Isolate.spawn(isolateFunction, receivePort.sendPort);

  SendPort sendPort = await receivePort.first;
  sendPort.send('ping');

  var response = await receivePort.first;
  print(response); // pong
}

Error Handling

Try-Catch

// Basic try-catch
try {
  var result = divide(10, 0);
  print(result);
} catch (e) {
  print('Error: $e');
}

// Catch specific types
try {
  var data = parseData();
} on FormatException catch (e) {
  print('Format error: $e');
} on Exception catch (e) {
  print('Exception: $e');
} catch (e) {
  print('Unknown error: $e');
}

Finally Block

// Finally always executes
void processFile() {
  var file;
  try {
    file = openFile();
    readFile(file);
  } catch (e) {
    print('Error: $e');
  } finally {
    file?.close();
  }
}

Custom Exceptions

// Define exception
class NetworkException implements Exception {
  final String message;
  NetworkException(this.message);

  
  String toString() => 'NetworkException: $message';
}

// Throw exception
void fetchData() {
  throw NetworkException('Connection failed');
}

// Catch custom exception
try {
  fetchData();
} on NetworkException catch (e) {
  print(e);
}

Generics

Generic Classes

// Generic class
class Box<T> {
  T value;
  Box(this.value);

  T getValue() => value;
}

// Usage
var intBox = Box<int>(42);
var stringBox = Box<String>('Hello');

print(intBox.getValue()); // 42
print(stringBox.getValue()); // Hello

Generic Methods

// Generic function
T getFirst<T>(List<T> items) {
  return items[0];
}

// Usage
var first = getFirst<int>([1, 2, 3]); // 1
var name = getFirst(['Alice', 'Bob']); // Alice

// Type constraint
T combine<T extends num>(T a, T b) {
  return (a + b) as T;
}

Generic Collections

// List of strings
List<String> names = ['Alice', 'Bob'];

// Map with typed keys/values
Map<String, int> ages = {
  'Alice': 30,
  'Bob': 25,
};

// Set of ints
Set<int> numbers = {1, 2, 3};

Dart CLI Commands

Running Code

# Run Dart file
dart run main.dart

# Run with arguments
dart run app.dart arg1 arg2

# Compile to native
dart compile exe main.dart

# Compile to JavaScript
dart compile js main.dart

# Run in AOT mode
dart compile aot-snapshot main.dart

Testing

# Run all tests
dart test

# Run specific test file
dart test test/widget_test.dart

# Run tests with coverage
dart test --coverage=coverage

# Generate coverage report
dart run coverage:format_coverage \
  --lcov \
  --in=coverage \
  --out=coverage/lcov.info

Analysis

# Analyze code
dart analyze

# Fix issues automatically
dart fix --apply

# Format code
dart format .

# Format and overwrite
dart format -o write .

# Check formatting
dart format --set-exit-if-changed .

Pub Package Manager

Package Commands

# Get dependencies
dart pub get

# Add package
dart pub add http

# Add dev dependency
dart pub add --dev test

# Remove package
dart pub remove http

# Upgrade packages
dart pub upgrade

# Outdated packages
dart pub outdated

Publishing

# Check package before publish
dart pub publish --dry-run

# Publish package
dart pub publish

# Get package info
dart pub deps

# Show dependency tree
dart pub deps --style=compact

pubspec.yaml

name: my_app
description: A sample app
version: 1.0.0

environment:
  sdk: ">=3.0.0 <4.0.0"

dependencies:
  http: ^1.0.0
  collection: ^1.17.0

dev_dependencies:
  test: ^1.24.0
  lints: ^3.0.0

String Operations

String Basics

// String literals
var single = 'Single quotes';
var double = "Double quotes";
var multiline = '''
Multiple
lines
''';

// String interpolation
var name = 'Alice';
var age = 30;
var msg = 'Name: $name, Age: $age';
var expr = 'Next year: ${age + 1}';

String Methods

var text = 'Hello, World!';

// Properties
text.length; // 13
text.isEmpty; // false
text.isNotEmpty; // true

// Methods
text.toUpperCase(); // HELLO, WORLD!
text.toLowerCase(); // hello, world!
text.contains('World'); // true
text.startsWith('Hello'); // true
text.endsWith('!'); // true
text.substring(0, 5); // Hello
text.split(','); // ['Hello', ' World!']
text.replaceAll('World', 'Dart'); // Hello, Dart!
text.trim(); // Remove whitespace

String Building

// StringBuffer for efficient concatenation
var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(' ');
buffer.write('World');
var result = buffer.toString(); // Hello World

// Join list
var words = ['Dart', 'is', 'awesome'];
var sentence = words.join(' '); // Dart is awesome

Pattern Matching

Switch Expressions

// Switch expression (Dart 3.0+)
String getMessage(int code) {
  return switch (code) {
    200 => 'OK',
    404 => 'Not Found',
    500 => 'Server Error',
    _ => 'Unknown',
  };
}

// With patterns
String describe(Object obj) {
  return switch (obj) {
    int() => 'Integer',
    String() => 'String',
    List() => 'List',
    _ => 'Other',
  };
}

If-Case

// If-case pattern matching
void checkValue(Object value) {
  if (value case int n when n > 0) {
    print('Positive integer: $n');
  } else if (value case String s) {
    print('String: $s');
  } else {
    print('Other type');
  }
}

Destructuring

// List patterns
var [a, b, c] = [1, 2, 3];

// Map patterns
var {'name': name, 'age': age} = {
  'name': 'Alice',
  'age': 30,
};

// Record patterns
var (x, y) = (10, 20);

// Rest pattern
var [first, ...rest] = [1, 2, 3, 4];
// first = 1, rest = [2, 3, 4]

Records (Dart 3.0+)

Record Basics

// Record literal
var record = (1, 2);
var named = (x: 10, y: 20);

// Access fields
print(record.$1); // 1
print(record.$2); // 2
print(named.x); // 10
print(named.y); // 20

// Mixed record
var mixed = (1, x: 'Hello');
print(mixed.$1); // 1
print(mixed.x); // Hello

Record Types

// Function returning record
(int, String) getUserInfo() {
  return (42, 'Alice');
}

var (id, name) = getUserInfo();

// Named record type
({int age, String name}) getUser() {
  return (age: 30, name: 'Bob');
}

var user = getUser();
print(user.name); // Bob

Record Patterns

// Destructuring
var (a, b) = (1, 2);

// In switch
switch (point) {
  case (0, 0):
    print('Origin');
  case (var x, 0):
    print('On x-axis: $x');
  case (0, var y):
    print('On y-axis: $y');
  case (var x, var y):
    print('Point: ($x, $y)');
}

Practical Examples

HTTP Request

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<Map<String, dynamic>> fetchUser(int id) async {
  final url = Uri.parse('https://api.example.com/users/$id');

  try {
    final response = await http.get(url);

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw Exception('Failed to load user');
    }
  } catch (e) {
    print('Error: $e');
    rethrow;
  }
}

// Usage
void main() async {
  var user = await fetchUser(1);
  print('Name: ${user['name']}');
}

JSON Serialization

import 'dart:convert';

class User {
  final String name;
  final int age;

  User(this.name, this.age);

  // From JSON
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      json['name'] as String,
      json['age'] as int,
    );
  }

  // To JSON
  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age,
    };
  }
}

// Usage
var json = '{"name": "Alice", "age": 30}';
var user = User.fromJson(jsonDecode(json));

var encoded = jsonEncode(user.toJson());
print(encoded); // {"name":"Alice","age":30}

File Operations

import 'dart:io';

// Read file
Future<String> readFile(String path) async {
  final file = File(path);
  return await file.readAsString();
}

// Write file
Future<void> writeFile(String path, String content) async {
  final file = File(path);
  await file.writeAsString(content);
}

// Check if exists
Future<bool> fileExists(String path) async {
  final file = File(path);
  return await file.exists();
}

// List directory
Future<void> listFiles(String path) async {
  final dir = Directory(path);
  await for (var entity in dir.list()) {
    print(entity.path);
  }
}

Stream Processing

import 'dart:async';

// Create stream from events
Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

// Transform and filter
Future<void> processStream() async {
  var stream = countStream(10)
      .where((n) => n % 2 == 0) // Even numbers only
      .map((n) => n * 2) // Double them
      .take(3); // First 3 results

  await for (var value in stream) {
    print(value); // 4, 8, 12
  }
}

// Reduce stream
Future<int> sumStream(Stream<int> stream) async {
  return await stream.reduce((a, b) => a + b);
}

Gotchas

Null Safety

  • Use ? for nullable types, not just optional parameters
  • Null assertion ! crashes if null - use carefully
  • Late variables must be initialized before access
  • Cannot assign null to non-nullable types

Async/Await

  • Forgetting async keyword prevents await usage
  • Unhandled async errors crash the app - always use try-catch
  • await only works in async functions
  • Don't use await in loops unless sequential processing needed

Collections

  • Lists are zero-indexed (first element is list[0])
  • Maps return null for missing keys (use ?? operator)
  • Sets don't allow duplicates - adding same value ignored
  • Collection literals create mutable collections

Type System

  • var infers type from assignment - cannot reassign different type
  • dynamic disables type checking - use sparingly
  • Generic types are erased at runtime (no is List<String>)
  • Integer division uses ~/ operator (5 ~/ 2 = 2)

Also see