Dart
null safety Β· async Β· futures Β· streams Β· classes Β· mixins Β· collections Β· Flutter
Sheet 2 of 3
Dart 3.x
Beginner
Printable
Variables & Null Safety
Variable Declaration
// Type inferred var name = 'Alice'; var age = 25; // Explicit type String city = 'Hyderabad'; int score = 92; double pi = 3.14159; bool active = true; // final β set once at runtime final url = 'https://api.example.com'; // const β compile-time constant const MAX = 100; // dynamic β opt out of type checking dynamic anything = 'hello'; anything = 42; // OK
Null Safety β ?, !, ??
// Non-nullable by default String a = 'hi'; // never null String? b = null; // nullable // ? β conditional member access b?.toUpperCase() // null if b is null b?.length // null if b is null // ! β null assertion (throws if null) String c = b!; // trust me, not null // ?? β if-null operator String d = b ?? 'default'; // ??= β assign if null b ??= 'assigned'; // late β initialise later (non-null) late String username; username = 'alice'; // before use
Type System & Conversions
// Check type age is int // true age is String // false age is! String // true // Cast dynamic v = 42; int n = v as int; // Parse strings int i = int.parse('42'); double d = double.parse('3.14'); int? x = int.tryParse('bad'); // x == null (doesn't throw) // Convert to string 42.toString() // '42' 3.14.toStringAsFixed(1) // '3.1'
Sound null safety is Dart's biggest feature since 2.12. The compiler guarantees that non-nullable variables cannot be null at runtime β no more null pointer exceptions if you follow the type system.
Collections
List
// Typed list List<int> nums = [1, 2, 3]; var fruits = ['apple', 'mango']; fruits.add('cherry'); fruits.remove('apple'); fruits.length // 2 fruits.first // 'mango' fruits.last // 'cherry' fruits.contains('mango') // true fruits.reversed.toList() // Spread operator var all = [...fruits, 'kiwi']; // Collection if / for var menu = [ 'Home', if (isAdmin) 'Admin', for (var p in pages) p.title, ];
Map & Set
// Map β key:value var user = { 'name': 'Alice', 'age': 25, }; user['email'] = 'a@b.com'; user.containsKey('age') // true user.remove('age') user.keys // Iterable of keys user.values // Iterable of values user.entries // Iterable of MapEntry // Set β unique values var tags = {'dart', 'flutter', 'dart'}; // {'dart', 'flutter'} β deduped tags.add('mobile'); tags.contains('flutter') // true tags.union({'web'}) tags.intersection(other)
Functions
Define & Call
// Regular function int add(int a, int b) { return a + b; } // Arrow β single expression int square(int x) => x * x; // Named parameters (with {}) void greet({required String name, String title = 'Mr'}) { print('Hello $title $name'); } greet(name: 'Alice'); greet(name: 'Bob', title: 'Dr'); // Optional positional (with []) String tag(String s, [String t = 'div']) => '<$t>$s</$t>'; // First-class / lambda var double = (int x) => x * 2; [1,2,3].map(double).toList() // [2, 4, 6]
String Interpolation & Cascade
var name = 'Alice'; var age = 25; // $ for variables print('Hello $name'); // ${} for expressions print('${name.toUpperCase()} is ${age+1}'); // Multi-line strings var text = ''' Hello World '''; // Cascade .. β chain calls var sb = StringBuffer() ..write('Hello') ..write(' World'); print(sb.toString());
Classes, Inheritance & Mixins
Class Definition
class Animal { String name; int age; // Constructor shorthand Animal(this.name, this.age); // Named constructor Animal.unknown() : name = 'Unknown', age = 0; // Getter String get info => '$name ($age)'; // Setter set newName(String n) { name = n.trim(); } void speak() => print('...'); @override String toString() => info; }
Inheritance & Interfaces
// extends β inherit one class class Dog extends Animal { String breed; Dog(String name, this.breed) : super(name, 0); @override void speak() => print('Woof!'); } // abstract class abstract class Shape { double area(); void describe() => print('area=${area()}'); } // implements β use as interface class Circle implements Shape { final double r; Circle(this.r); @override double area() => 3.14159 * r * r; }
Mixins
// mixin β reusable behaviour mixin Flyable { void fly() => print('Flying!'); int speed = 100; } mixin Swimmable { void swim() => print('Swimming!'); } // with β apply mixins class Duck extends Animal with Flyable, Swimmable { Duck() : super('Duck', 1); } var d = Duck(); d.fly(); // Flying! d.swim(); // Swimming! // on β restrict mixin to type mixin Trainable on Animal { void train() => print('$name trained'); }
Dart's this.field shorthand in constructors β Animal(this.name, this.age) β automatically assigns parameters to fields. No boilerplate needed.
Async / Await & Futures
Future & async/await
// Future β value arriving later Future<String> fetchUser(int id) async { await Future.delayed( Duration(seconds: 1)); return 'User $id'; } void main() async { String user = await fetchUser(1); print(user); // 'User 1' } Future.value(42) Future.error('oops') // Run multiple in parallel var results = await Future.wait([ fetchUser(1), fetchUser(2), ]);
Error Handling in Async
Future<void> loadData() async { try { var data = await fetchUser(1); print(data); } catch (e) { print('Error: $e'); } finally { print('Done'); } } // .then() chain fetchUser(1) .then((u) => print(u)) .catchError((e) => print(e)) .whenComplete(() => print('done')); // FutureOr β sync or async FutureOr<String> maybeAsync() => 'immediate';
Streams
// async* + yield β Stream Stream<int> count(int n) async* { for (var i=1; i<=n; i++) { await Future.delayed( Duration(seconds: 1)); yield i; } } // await for β consume stream await for (var n in count(3)) print(n); // 1, 2, 3 // Stream from Iterable Stream.fromIterable([1,2,3]) .map((x) => x * 2) .where((x) => x > 2) .listen((x) => print(x)); // StreamController var ctrl = StreamController<int>(); ctrl.sink.add(1); ctrl.stream.listen((v) => print(v)); ctrl.close();
async* + yield creates a generator function that produces a Stream. Use sync* + yield for synchronous Iterable generators.
Control Flow
if / switch / loops
if (score >= 90) { print('A'); } else if (score >= 80) { print('B'); } // switch (Dart 3 β exhaustive) switch (day) { case 'Mon' || 'Fri': print('Busy'); case 'Sat' || 'Sun': print('Weekend'); default: print('Normal'); } for (var fruit in fruits) print(fruit); fruits.forEach((f) => print(f)); while (n > 0) { n--; } do { n++; } while (n < 10);
Pattern Matching (Dart 3)
// switch expression var grade = switch (score) { >= 90 => 'A', >= 80 => 'B', >= 70 => 'C', _ => 'F', }; // Destructure records (Dart 3) var point = (10, 20); var (x, y) = point; // x=10, y=20 // Pattern in if if (obj case String s) { print('String: $s'); }
Enums & Records
Enhanced Enums (Dart 2.17+)
// Simple enum enum Direction { north, south, east, west } var d = Direction.north; d.name // 'north' d.index // 0 // Enhanced enum β with members enum Status { active('Active', 200), inactive('Inactive', 404); final String label; final int code; const Status(this.label, this.code); } Status.active.label // 'Active' Status.active.code // 200
Records (Dart 3)
// Record β anonymous immutable struct var point = (x: 10, y: 20); point.x // 10 point.y // 20 // Return multiple values (String, int) getUser() => ('Alice', 25); var (name, age) = getUser(); // typedef β name a type typedef Point = ({int x, int y}); Point p = (x: 3, y: 4);
Flutter Essentials
StatelessWidget & StatefulWidget
import 'package:flutter/material.dart'; // Stateless β no mutable state class HelloWidget extends StatelessWidget { final String name; const HelloWidget({super.key, required this.name}); @override Widget build(BuildContext ctx) => Text('Hello $name'); } // Stateful β mutable state class Counter extends StatefulWidget { const Counter({super.key}); @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int _count = 0; @override Widget build(BuildContext ctx) => ElevatedButton( onPressed: () => setState(() => _count++), child: Text('$_count'), ); }
Common Widgets
Scaffold( appBar: AppBar(title: Text('App')), body: Column( children: [ Text('Hello'), SizedBox(height: 16), ElevatedButton( onPressed: (){}, child: Text('Click'), ), ], ), ) Row(children:[...]) Stack(children:[...]) ListView.builder( itemCount: items.length, itemBuilder: (ctx, i) => Text(items[i]), ) Container( width: 100, height: 100, color: Colors.blue, padding: EdgeInsets.all(8), )
FutureBuilder & StreamBuilder
FutureBuilder<String>( future: fetchUser(1), builder: (ctx, snap) { if (snap.hasError) return Text('Error'); if (!snap.hasData) return CircularProgressIndicator(); return Text(snap.data!); }, ) StreamBuilder<int>( stream: count(5), builder: (ctx, snap) { if (!snap.hasData) return CircularProgressIndicator(); return Text('${snap.data}'); }, )
Always use const constructors for widgets that don't change β const Text('hi'). Flutter reuses them across rebuilds, dramatically improving performance.
Dart Mastery Checklist
| Null Safety & Types | Key point |
|---|---|
| Nullable type | String? name |
| Safe access | obj?.field |
| Fallback value | val ?? 'default' |
| Assert non-null | val! |
| Late initialise | late String x; |
| Type check | obj is String |
| Async & Streams | Key point |
|---|---|
| Async function | Future<T> fn() async |
| Await a future | var x = await fn(); |
| Parallel futures | Future.wait([...]) |
| Create a stream | async* { yield x; } |
| Consume a stream | await for (var x in s) |
| Error handling | try/catch with await |
| Classes & Flutter | Key point |
|---|---|
| Constructor shorthand | Cls(this.field) |
| Inherit behaviour | mixin + with |
| Stateless widget | extends StatelessWidget |
| Trigger rebuild | setState(() {...}) |
| Async in UI | FutureBuilder |
| Live data in UI | StreamBuilder |
Next up β Sheet 3: Shell Scripting Β·
bash Β· variables Β· loops Β· pipes Β· cron Β· grep Β· sed Β· awk