- Khai báo generic class
Box<T>và generic methodT first<T>(List<T> xs). - Hiểu bounded generic
T extends Comparable. - Viết extension method để thêm function cho built-in type.
- Hiểu extension types (Dart 3.3+) như zero-cost wrapper.
- Sử dụng records
(int, String)và named records({int x, int y}). - Dùng patterns: destructuring, switch with pattern, if-case.
- Kết hợp sealed class + exhaustive switch.
- Hiểu khi nào dùng record vs class.
1. Generic class
class Box<T> {
final T value;
Box(this.value);
T unwrap() => value;
}
void main() {
final intBox = Box<int>(42);
final strBox = Box<String>('hello');
int i = intBox.unwrap(); // type safe
String s = strBox.unwrap();
// String x = intBox.unwrap(); // ❌ Lỗi compile
}
Use case generic class:
- Container:
List<T>,Map<K, V>,Stream<T>. - Repository:
Repository<User>,Repository<Post>. - Response wrapper:
ApiResponse<T>. - Cache:
Cache<K, V>.
2. Generic method
T first<T>(List<T> xs) => xs.first;
void main() {
int a = first([1, 2, 3]); // inferred T = int
String b = first(['a', 'b']); // inferred T = String
// Explicit
var c = first<int>([1, 2]);
}
// Generic method khác generic class — chỉ method có type param
class Util {
static T first<T>(List<T> xs) => xs.first;
static List<R> mapAll<T, R>(List<T> xs, R Function(T) f) =>
xs.map(f).toList();
}
3. Bounded generic
// T phải implement Comparable<T>
T max<T extends Comparable<T>>(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
print(max(5, 3)); // 5
print(max('apple', 'banana')); // banana
// max(true, false); // ❌ bool không extends Comparable
4. Extension method
Thêm method cho type sẵn có — kể cả built-in int, String, hoặc class third-party.
extension StringX on String {
String get reverse => split('').reversed.join();
bool get isPalindrome => this == reverse;
String repeat(int n) => List.filled(n, this).join();
}
extension IntX on int {
String toVnd() => '$this VND';
Duration get seconds => Duration(seconds: this);
Duration get minutes => Duration(minutes: this);
}
void main() {
print('racecar'.isPalindrome); // true
print('ab'.repeat(3)); // ababab
print(1000.toVnd()); // 1000 VND
await Future.delayed(2.seconds); // đẹp như Kotlin/Swift
}
Extension method không override method gốc — nếu trùng tên, instance method gốc thắng:
extension on String {
String toLowerCase() => 'extension';
}
print('HELLO'.toLowerCase()); // 'hello' — method gốc thắng
5. Extension types (Dart 3.3+)
Khác extension method. Extension type tạo type mới chỉ tồn tại tại compile-time — runtime vẫn là type gốc. Zero-cost wrapper.
extension type Email(String value) {
bool get isValid => value.contains('@');
String get domain => value.split('@').last;
}
void main() {
final e = Email('viet@example.com');
print(e.isValid); // true
print(e.domain); // example.com
// Compile-time type khác String
String s = 'plain';
// Email e2 = s; // ❌ String không assign vào Email
// String s2 = e; // ❌ Email không assign vào String
// Runtime — vẫn là String
print(e.runtimeType); // String
print(identical(e as Object, 'viet@example.com' as Object)); // true
}
Use case extension type:
- Domain primitive:
UserId,Email,Url— không nhầm với String thường. - Validation enforce: chỉ tạo qua factory, đảm bảo valid.
- Performance: không boxing như class wrapper truyền thống.
| Extension method | Extension type | |
|---|---|---|
| Tạo type mới? | Không | Có (compile-time) |
| Runtime cost | 0 | 0 (boxing-free) |
| Use case | Thêm helper method | Domain type-safety |
| Type check | String vẫn là String | Email != String compile-time |
6. Records
Records (Dart 3) = anonymous tuple/struct. Structural equality. Lightweight alternative cho class khi chỉ cần "túi data".
Positional
final r = (1, 'a'); // type: (int, String)
print(r.$1); // 1
print(r.$2); // 'a'
// Mixed type OK
final mix = (42, 'hi', true); // (int, String, bool)
Named
final user = (name: 'Việt', age: 25);
print(user.name); // 'Việt'
print(user.age); // 25
// Type: ({String name, int age})
({String name, int age}) makeUser() => (name: 'Việt', age: 25);
// Mix positional + named
final mixed = (1, 2, label: 'x');
print(mixed.$1); // 1
print(mixed.label); // 'x'
Structural equality
final a = (1, 'a');
final b = (1, 'a');
print(a == b); // true — so theo nội dung, không identity
// Khác class
final p1 = Point(1, 2);
final p2 = Point(1, 2);
print(p1 == p2); // false (trừ khi override ==)
Record vs class — khi nào dùng?
| Record | Class | |
|---|---|---|
| Equality | Structural (auto) | Identity (cần override ==) |
| Mutable | Không | Tuỳ field |
| Method | Không có (chỉ field) | Có |
| Identity stable | Không | Có |
| Use case | Return multiple value, intermediate state | Domain entity, behavior |
7. Patterns — destructuring
Destructure record
(int, String) parseLine(String line) {
final parts = line.split(':');
return (int.parse(parts[0]), parts[1]);
}
// Destructure
final (id, name) = parseLine('1:Việt');
print('id=$id, name=$name'); // id=1, name=Việt
// Destructure named
final (name: n, age: a) = (name: 'Việt', age: 25);
print('$n $a');
List pattern
final xs = [1, 2, 3, 4, 5];
final [first, second, ...rest] = xs;
print(first); // 1
print(second); // 2
print(rest); // [3, 4, 5]
Map pattern
final json = {'name': 'Việt', 'age': 25};
final {'name': name, 'age': age} = json;
print('$name $age');
8. Pattern trong switch
sealed class Shape {}
class Circle extends Shape {
final double radius;
Circle(this.radius);
}
class Square extends Shape {
final double side;
Square(this.side);
}
double area(Shape s) => switch (s) {
Circle(:var radius) => 3.14 * radius * radius,
Square(:var side) => side * side,
};
// Sealed + exhaustive — không cần default!
9. Pattern + when guard
String describePoint((int, int) p) => switch (p) {
(0, 0) => 'origin',
(var x, 0) => 'on x-axis: $x',
(0, var y) => 'on y-axis: $y',
(var x, var y) when x == y => 'on diagonal at $x',
_ => 'normal point',
};
print(describePoint((0, 0))); // origin
print(describePoint((5, 0))); // on x-axis: 5
print(describePoint((3, 3))); // on diagonal at 3
10. if-case
final json = {'name': 'Việt', 'age': 25};
if (json case {'name': String name, 'age': int age}) {
print('Valid user: $name $age');
} else {
print('Invalid format');
}
// Combine với pattern type check
final input = '42';
if (int.tryParse(input) case int n when n > 0) {
print('positive: $n');
}
11. Sealed + exhaustive switch — pattern chuẩn
sealed class ApiState<T> {
const ApiState();
}
class Idle<T> extends ApiState<T> {
const Idle();
}
class Loading<T> extends ApiState<T> {
const Loading();
}
class Success<T> extends ApiState<T> {
final T data;
const Success(this.data);
}
class Failure<T> extends ApiState<T> {
final String message;
const Failure(this.message);
}
String describe(ApiState<User> state) => switch (state) {
Idle() => 'chưa load',
Loading() => 'đang load...',
Success(:var data) => 'load xong: ${data.name}',
Failure(:var message) => 'lỗi: $message',
};
// Compiler verify đủ 4 case — không cần default
Trước Dart 3 (hoặc Java/JS), bạn viết:
if (state is Loading) ...
else if (state is Success) print(state.data);
else if (state is Failure) ...
else { // Idle... }
Pattern matching ngắn hơn + compiler enforce exhaustive. Forget case → compile fail. Code maintain dễ hơn nhiều.
12. Object pattern
class Point {
final int x, y;
Point(this.x, this.y);
}
String describe(Point p) => switch (p) {
Point(x: 0, y: 0) => 'origin',
Point(:var x, :var y) when x == y => 'diagonal: $x',
Point(:var x, :var y) => '($x, $y)',
};
Point(:var x, :var y) = Point(x: var x, y: var y) shorthand. : bind tự động tên field thành biến cùng tên.
13. Khi nào dùng feature nào?
- Generic class: khi container cần work với nhiều type.
- Generic method: khi function nhận arg + return có type relation.
- Bounded generic: khi cần gọi method của type bound (vd Comparable).
- Extension method: thêm helper cho built-in / third-party type, fluent API.
- Extension type: domain primitive — Email, UserId, Url khác String.
- Records: return multiple value, intermediate state, "anonymous DTO".
- Class: domain entity có behavior, identity, lifecycle.
- Sealed + pattern match: state machine, Result type, AST node.
- If-case: validate single shape, không cần multiple case.
14. Bài tập
Generic Stack<T>
Implement class Stack<T> với push, pop, peek, isEmpty. Test với Stack<int> và Stack<String>.
💡 Gợi ý đáp án
class Stack<T> {
final _items = <T>[];
void push(T item) => _items.add(item);
T pop() => _items.removeLast();
T peek() => _items.last;
bool get isEmpty => _items.isEmpty;
int get length => _items.length;
}
void main() {
final ints = Stack<int>()..push(1)..push(2)..push(3);
print(ints.pop()); // 3
print(ints.peek()); // 2
final strs = Stack<String>()..push('a')..push('b');
print(strs.pop()); // 'b'
}
DateTime extensions
Viết extension DateTimeX on DateTime với isToday, isYesterday, relative (vd "3 hours ago").
💡 Gợi ý đáp án
extension DateTimeX on DateTime {
bool get isToday {
final now = DateTime.now();
return year == now.year && month == now.month && day == now.day;
}
bool get isYesterday {
final yesterday = DateTime.now().subtract(Duration(days: 1));
return year == yesterday.year && month == yesterday.month && day == yesterday.day;
}
String get relative {
final diff = DateTime.now().difference(this);
if (diff.inMinutes < 1) return 'vừa xong';
if (diff.inHours < 1) return '${diff.inMinutes} phút trước';
if (diff.inDays < 1) return '${diff.inHours} giờ trước';
if (diff.inDays < 30) return '${diff.inDays} ngày trước';
return toIso8601String();
}
}
Map → record refactor
Refactor function trả Map<String, dynamic> chứa 3 field thành function trả record
(String name, int age, bool premium). So sánh code caller.
💡 Gợi ý đáp án
// Trước (dynamic)
Map<String, dynamic> getUser() {
return {'name': 'Việt', 'age': 25, 'premium': true};
}
final u = getUser();
print(u['name']); // dynamic, không type-safe
// Sau (record)
({String name, int age, bool premium}) getUser() {
return (name: 'Việt', age: 25, premium: true);
}
final u = getUser();
print(u.name); // String — type-safe
print(u.age);
// Destructure
final (name: n, age: a, premium: p) = getUser();
Sealed ApiState<T>
Implement sealed class ApiState<T> với 4 case: Idle, Loading, Success(T data), Error(String message).
Viết String describe(ApiState state) dùng switch exhaustive.
💡 Gợi ý đáp án
sealed class ApiState<T> { const ApiState(); }
class Idle<T> extends ApiState<T> { const Idle(); }
class Loading<T> extends ApiState<T> { const Loading(); }
class Success<T> extends ApiState<T> {
final T data;
const Success(this.data);
}
class Error<T> extends ApiState<T> {
final String message;
const Error(this.message);
}
String describe(ApiState<String> s) => switch (s) {
Idle() => 'chưa load',
Loading() => 'đang load',
Success(:var data) => 'kết quả: $data',
Error(:var message) => 'lỗi: $message',
};
void main() {
print(describe(Idle()));
print(describe(Loading()));
print(describe(Success('hello')));
print(describe(Error('network')));
}
Validate JSON với if-case
Validate JSON shape với if-case: nhận Map<String, dynamic>, return User? nếu hợp lệ shape
{name: String, age: int}. Không dùng if/else chain.
💡 Gợi ý đáp án
class User { final String name; final int age; User(this.name, this.age); }
User? parseUser(Map<String, dynamic> json) {
if (json case {'name': String name, 'age': int age}) {
return User(name, age);
}
return null;
}
void main() {
print(parseUser({'name': 'Việt', 'age': 25})); // User
print(parseUser({'name': 'Việt'})); // null
print(parseUser({'name': 'Việt', 'age': 'thirty'})); // null (wrong type)
}
Single line check + bind 2 variable cùng lúc. Sạch hơn nhiều so với chain check.
15. Quiz
T first<T>(List<T> xs) — gọi first([1, 2, 3]) type là gì?
Xem đáp án
Đáp án: int. Inferred từ argument List<int>.
Extension thêm method toVnd cho int — instance method gốc trùng tên thì sao?
Xem đáp án
Đáp án: Method gốc thắng. Extension không override built-in method. Đặt tên unique để tránh conflict.
Extension type Email runtime là gì?
Xem đáp án
Đáp án: String (boxing-free). Compile-time type khác String, nhưng runtime không có wrapper object — performance bằng String thường.
(1, 'a') == (1, 'a') — true hay false?
Xem đáp án
Đáp án: true. Record equality structural — so theo nội dung.
({int age, String name}) và ({String name, int age}) — same type không?
Xem đáp án
Đáp án: Có. Named record so theo tên + type, không thứ tự. Khác positional record (thứ tự matter).
Sealed class trong file A, switch ở file B không cover hết case — analyzer cảnh báo không?
Xem đáp án
Đáp án: Có. Sealed enforce exhaustive — compiler biết tất cả subclass (chỉ trong cùng library A), check switch B đủ chưa.
case Point(:var x, :var y) ≡ case Point(x: var x, y: var y)?
Xem đáp án
Đáp án: Có. :var x shorthand cho x: var x (bind field name vào biến cùng tên).
Khi nào nên dùng record thay class?
Xem đáp án
Đáp án: Data tạm thời, không cần method/identity/behavior. Đại diện: function return nhiều giá trị, intermediate state. Class khi có domain identity, behavior, lifecycle.
16. Tổng kết Dart curriculum
Bạn đã hoàn thành 8 chương Dart. Chương trình bao gồm:
- Hello Dart — setup, CLI, project structure.
- Variables & Types — type system, var/final/const, null safety basic.
- Functions & Control Flow — named param, closure, switch expression.
- Classes & Inheritance — constructor 5 loại, mixin, class modifier Dart 3.
- Null Safety & Error Handling — sound null safety, late, exception.
- Collections & Iterables — lazy Iterable, higher-order, collection-if/for.
- Async, Future & Stream — event loop, Future, Stream, Isolate.
- Generics, Extension, Patterns — generic, extension type, record, pattern matching.
Mọi Dart pattern Flutter dùng đã trong tay. Flutter sub-pillar sẽ áp dụng:
- Named params +
required(chương 3) → mọi widget constructor. - Class extends (chương 4) → StatelessWidget, StatefulWidget.
- Stream (chương 7) → Bloc state, StreamBuilder.
- Sealed + pattern (chương 8) → Bloc state class.
- copyWith (chương 4) → immutable state.
17. Tiếp tục — Flutter sub-pillar
Sang Flutter sub-pillar (15 chương) để build app cross-platform với Dart. Bắt đầu từ Flutter Setup → Widget Tree → Layout → Theming → ... → Performance/Deploy.