Chương 03 · Functions & Control Flow

Functions & Control Flow

Function trong Dart có cú pháp đặc biệt — named parameters + required keyword. Đây chính là pattern xuất hiện ở mọi widget constructor Flutter. Không nắm chương này, đọc code Flutter sẽ confused. Switch expression Dart 3 cũng là cú pháp mới sẽ gặp khắp nơi.

Độ dài: ~930 dòng Bài tập: 5 Quiz: 8 Prerequisites: Chương 2 (types)
🎯 Mục tiêu chương
  • Khai báo function với positional, optional positional, và named parameters.
  • Hiểu required keyword cho named param.
  • Dùng arrow function syntax => cho function ngắn.
  • Function là first-class: gán biến, truyền argument, return function.
  • Viết closure và hiểu capture biến.
  • Dùng control flow đầy đủ: if, switch (cũ + Dart 3 expression), for, while, break, continue, label.
  • Hiểu assert và khi nào dùng.

1. Function declaration cơ bản

Cú pháp chuẩn:

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

void main() {
  print(add(2, 3));  // 5
}

Phân tích:

  • Return type (int) — đứng trước tên. void nếu không trả gì.
  • Tên function — camelCase idiomatic.
  • Parameter list — mỗi param có type + tên.
  • Body — trong { }, có return nếu return type khác void.
🔀 So với JS / TS
// JS
function add(a, b) { return a + b; }
const add2 = (a, b) => a + b;

// TS
function add(a: number, b: number): number { return a + b; }

// Dart — gần TS, return type đứng đầu
int add(int a, int b) => a + b;

2. Positional parameters

Default — param bắt buộc theo thứ tự. Caller phải truyền đủ và đúng order:

void greet(String firstName, String lastName) {
  print('Xin chào $firstName $lastName');
}

greet('Việt', 'Nguyễn');   // Xin chào Việt Nguyễn
// greet('Việt');                // ❌ Thiếu lastName
// greet('Nguyễn', 'Việt');      // ✅ Compile OK nhưng SAI ý nghĩa
🔥 Vấn đề positional khi nhiều param

Function 5+ positional param khó đọc:

connectToServer('api.example.com', 443, true, 30, false);

true? false? 30? Đây là lúc cần named parameters.

3. Optional positional parameters

Wrap param trong [ ] để optional:

String greet(String name, [String? title]) {
  if (title != null) return '$title $name';
  return 'Xin chào $name';
}

greet('Việt');            // Xin chào Việt
greet('Việt', 'Anh');    // Anh Việt

Có thể có default value:

int add(int a, [int b = 0, int c = 0]) {
  return a + b + c;
}

print(add(1));         // 1
print(add(1, 2));      // 3
print(add(1, 2, 3));   // 6

4. Named parameters + required

Đây là pattern bạn sẽ gặp ở MỌI widget Flutter. Wrap trong { }:

void connectToServer({
  required String host,
  int port = 8080,
  bool secure = false,
  Duration timeout = const Duration(seconds: 30),
}) {
  print('Connect $host:$port secure=$secure timeout=$timeout');
}

// Gọi — chỉ truyền những gì cần, theo thứ tự bất kỳ
connectToServer(host: 'api.example.com');
connectToServer(host: 'api.example.com', port: 443, secure: true);
connectToServer(secure: true, host: 'api.example.com');  // thứ tự đảo OK

Quy tắc required:

  • Named param nullable (T?): mặc định optional, không cần required.
  • Named param non-nullable không default: BẮT BUỘC required.
  • Named param non-nullable có default: không cần required (vì có default).
void demo({
  String? a,                     // OK — nullable, optional
  required String b,           // OK — required keyword
  String c = 'default',         // OK — non-null nhưng có default
  // String d,                  // ❌ Lỗi compile — non-null, no default, no required
}) {}
🧠 Vì sao Flutter widget dùng named param?

Widget Flutter thường có 10-30+ properties. Nếu positional thì caller phải nhớ thứ tự — không khả thi. Named param + required = mọi widget chỉ rõ param nào bắt buộc, param nào optional, có default. Code đọc như JSON.

ElevatedButton(
  onPressed: () => print('tap'),
  style: ElevatedButton.styleFrom(...),
  child: const Text('Submit'),
)

5. Arrow function — shorthand

Function 1 expression có thể viết gọn:

// Cách dài
int square(int x) {
  return x * x;
}

// Cách ngắn — arrow
int square(int x) => x * x;

Note: chỉ 1 expression, không phải block:

// ❌ Sai — block trong arrow
// int square(int x) => { return x * x; };

// ✅ Đúng — expression
int square(int x) => x * x;

// ✅ Cũng OK — gọi method, in-line conditional
String grade(int score) => score > 50 ? 'pass' : 'fail';

6. Anonymous function / lambda

Function không tên — dùng inline:

// Truyền vào higher-order function
final doubled = [1, 2, 3].map((x) => x * 2).toList();
print(doubled);  // [2, 4, 6]

// Hoặc block
[1, 2, 3].forEach((x) {
  print('item: $x');
});

// Lưu vào biến
final isEven = (int n) => n % 2 == 0;
print(isEven(4));  // true

7. Function là first-class value

Dart treat function như object thường — có type, gán biến được, truyền argument, return từ function khác.

int square(int x) => x * x;

void main() {
  // Gán function vào biến
  var f = square;          // type: int Function(int)
  print(f(5));             // 25

  // Truyền vào higher-order
  final xs = [1, 2, 3].map(square).toList();
  print(xs);              // [1, 4, 9]
}

Function type — viết explicit

// Type của function
int Function(int) f = square;
bool Function(int) isEven = (n) => n % 2 == 0;

// typedef — alias cho function type
typedef IntTransformer = int Function(int);

IntTransformer addOne = (x) => x + 1;
IntTransformer mulTwo = (x) => x * 2;

// Higher-order: function nhận + return function
IntTransformer compose(IntTransformer f, IntTransformer g) {
  return (x) => f(g(x));
}

final plus1Mul2 = compose(mulTwo, addOne);
print(plus1Mul2(3));  // (3+1)*2 = 8

8. Closure — function giữ scope ngoài

Closure là function lambda capture biến từ scope bên ngoài:

Function makeCounter() {
  var count = 0;
  return () {
    count++;
    return count;
  };
}

final counter = makeCounter();
print(counter());  // 1
print(counter());  // 2
print(counter());  // 3 — count được giữ giữa các lần gọi!

// Mỗi gọi makeCounter() tạo closure ĐỘC LẬP
final a = makeCounter();
final b = makeCounter();
print(a());  // 1
print(a());  // 2
print(b());  // 1 — riêng counter cho b

Pattern factory với closure:

int Function(int) multiplier(int factor) {
  return (x) => x * factor;  // capture factor
}

final double_ = multiplier(2);
final triple = multiplier(3);
print(double_(5));  // 10
print(triple(5));   // 15
🧠 Capture by reference, không phải value

Closure capture biến qua reference. Khi biến outer thay đổi, closure thấy giá trị mới. Ví dụ:

var x = 10;
final getX = () => x;
print(getX());  // 10

x = 20;
print(getX());  // 20 — không phải 10!

9. Control flow — if / else

Standard, chỉ điểm:

if (score >= 90) {
  print('A');
} else if (score >= 80) {
  print('B');
} else {
  print('C');
}

// Ternary
final grade = score >= 50 ? 'pass' : 'fail';

// Lưu ý: điều kiện PHẢI là bool — không có truthy/falsy
// if (someList) { }   // ❌
// if (someList.isNotEmpty) { }  // ✅

10. Switch statement cổ điển

void describe(int day) {
  switch (day) {
    case 1:
      print('Thứ Hai');
      break;
    case 2:
      print('Thứ Ba');
      break;
    default:
      print('Ngày khác');
  }
}
💡 Dart không fallthrough mặc định

Khác C/Java — mỗi case tự động break. break trong code trên thực ra không cần (analyzer cảnh báo). Để fallthrough phải dùng continue label;.

11. Switch expression Dart 3

Cú pháp mới — switch trả value:

String describeStatus(int code) {
  return switch (code) {
    200 => 'OK',
    201 => 'Created',
    301 || 302 => 'Redirect',
    404 => 'Not Found',
    >= 500 => 'Server Error',
    _ => 'Unknown',
  };
}

print(describeStatus(200));  // OK
print(describeStatus(302));  // Redirect
print(describeStatus(503));  // Server Error

Features:

  • Trả value — assignable.
  • => arrow cho mỗi case.
  • Multiple values với ||.
  • Relational pattern: >= 500, < 100.
  • Wildcard _ cho default.
  • Exhaustive check — analyzer ép cover hết case (đặc biệt với sealed class).
📘 Chương 8 đi sâu pattern matching

Đây mới là switch expression cơ bản. Chương 8 sẽ học destructuring, object pattern, sealed class + exhaustive switch.

12. For / for-in / forEach

// For index — quen từ C
for (var i = 0; i < 5; i++) {
  print(i);
}

// For-in — iterate
final names = ['a', 'b', 'c'];
for (final name in names) {
  print(name);
}

// forEach method — function-style
names.forEach(print);
names.forEach((n) => print('name: $n'));

// Map iterate
final ages = {'Việt': 25, 'Anh': 30};
for (final entry in ages.entries) {
  print('${entry.key}: ${entry.value}');
}
🔥 forEach không break được

forEach là method, không phải statement. Không có break:

// ❌ Không work — break không hợp lệ ngoài loop
names.forEach((n) {
  if (n == 'b') break;  // Lỗi compile
});

// ✅ Dùng for-in nếu cần break
for (final n in names) {
  if (n == 'b') break;
  print(n);
}

13. While / do-while

var i = 0;
while (i < 5) {
  print(i);
  i++;
}

// do-while — body chạy ít nhất 1 lần
var j = 10;
do {
  print(j);
  j--;
} while (j > 5);

14. break, continue, label

// Vòng lồng — label để break từ vòng trong
outer: for (var i = 0; i < 5; i++) {
  for (var j = 0; j < 5; j++) {
    if (i == 2 && j == 3) {
      break outer;   // thoát cả 2 vòng
    }
    print('$i, $j');
  }
}

// continue — skip đến iteration tiếp theo
for (var n = 0; n < 10; n++) {
  if (n % 2 == 0) continue;
  print(n);  // chỉ in số lẻ
}

15. assert — debug check

assert kiểm tra điều kiện. Chỉ chạy trong debug/JIT mode. Production AOT build skip hoàn toàn.

int divide(int a, int b) {
  assert(b != 0, 'Divisor must not be zero');
  return a ~/ b;
}

divide(10, 2);  // 5
divide(10, 0);  // Debug: AssertionError. Production: phép chia 0 → exception khác.
⚠️ assert ≠ runtime validation

assert bị skip ở production, đừng dùng nó để validate input user. Use cases:

  • Sanity check internal — invariant của module.
  • Catch dev bug sớm (truyền null vào nơi không nên).
  • Document expected condition (assert is documentation).

Validation input user phải dùng throw + exception thường (chương 5).

16. Bài tập

Named param + required

Viết function connectToServer({required String host, int port = 8080, bool secure = false}). Gọi 3 cách khác nhau và in kết quả.

💡 Gợi ý đáp án
void connectToServer({
  required String host,
  int port = 8080,
  bool secure = false,
}) {
  final protocol = secure ? 'https' : 'http';
  print('Connect $protocol://$host:$port');
}

void main() {
  connectToServer(host: 'api.example.com');
  connectToServer(host: 'api.example.com', port: 443, secure: true);
  connectToServer(secure: true, host: 'api.example.com');
}

Multiplier với closure

Viết Function makeMultiplier(int factor) trả về function nhân factor. Test với final double_ = makeMultiplier(2); double_(5) → 10.

💡 Gợi ý đáp án
int Function(int) makeMultiplier(int factor) {
  return (x) => x * factor;
}

void main() {
  final double_ = makeMultiplier(2);
  final triple = makeMultiplier(3);

  print(double_(5));   // 10
  print(triple(5));    // 15
  print(double_(10));  // 20
}

Note: type int Function(int) rõ hơn dùng Function generic — type-safe.

Switch expression mapping HTTP status

Cho list status code [200, 201, 301, 404, 500], dùng switch expression mapping ra ['ok', 'created', 'redirect', 'not found', 'error'].

💡 Gợi ý đáp án
String describe(int code) => switch (code) {
  200 => 'ok',
  201 => 'created',
  300 || 301 || 302 => 'redirect',
  404 => 'not found',
  >= 500 => 'error',
  _ => 'unknown',
};

void main() {
  final codes = [200, 201, 301, 404, 500];
  final descs = codes.map(describe).toList();
  print(descs);  // [ok, created, redirect, not found, error]
}

Closure bug fix

Cho đoạn code:

List<Function> makeAdders() {
  final adders = <Function>[];
  for (var i = 0; i < 3; i++) {
    adders.add(() => i);
  }
  return adders;
}

void main() {
  final fs = makeAdders();
  for (final f in fs) {
    print(f());  // mong đợi: 0, 1, 2
  }
}

Output thực tế là gì? Fix nó để in 0, 1, 2.

💡 Gợi ý đáp án

Output: 0, 1, 2 — không phải bug! Dart capture biến by reference theo iteration của for. Mỗi iteration có biến i mới (do var i trong for-init).

Khác JavaScript với var (function scope) — JS gặp bug này, print 3, 3, 3. Phải dùng let để fix.

Bài học: Dart for-loop scope đúng. Closure capture đúng biến iteration. Không cần fix.

Sum với for vs fold

Viết function lấy List<int> trả về sum. Implement 2 cách: (a) for-in loop manual, (b) dùng .fold. So sánh code length và readability.

💡 Gợi ý đáp án
// (a) For-in
int sumLoop(List<int> xs) {
  var total = 0;
  for (final x in xs) {
    total += x;
  }
  return total;
}

// (b) fold — function style
int sumFold(List<int> xs) => xs.fold(0, (a, b) => a + b);

// Hoặc dùng reduce nếu list không rỗng
int sumReduce(List<int> xs) => xs.reduce((a, b) => a + b);

void main() {
  print(sumLoop([1, 2, 3, 4]));    // 10
  print(sumFold([1, 2, 3, 4]));    // 10
  print(sumReduce([1, 2, 3, 4])); // 10
}

.fold ngắn hơn nhưng phải biết signature. For-in dễ đọc cho beginner. Chương 6 sẽ dùng .fold nhiều hơn.

17. Quiz

Q1

Named parameter nullable mặc định có cần required không?

Xem đáp án

Đáp án: Không. String? đã imply optional (có thể null). required chỉ cần khi non-nullable không có default.

Q2

Named parameter non-nullable không có default — cần gì?

Xem đáp án

Đáp án: required keyword. Nếu không có → lỗi compile vì biến phải có giá trị trước khi đọc (definite assignment).

Q3

Dart switch có fallthrough mặc định như C không?

Xem đáp án

Đáp án: Không. Mỗi case auto-break. Khác C/Java. Fallthrough phải explicit qua continue label;.

Q4

Arrow function => body có thể chứa nhiều statement không?

Xem đáp án

Đáp án: Không. Chỉ 1 expression. Cần nhiều statement → dùng { ... } block. Không thể (x) => { stmt1; stmt2; }.

Q5

void Function()Function khác gì?

Xem đáp án

Đáp án: void Function() là type cụ thể — function không param, không return. Function là parent type generic — mất type safety, không biết signature.

Q6

Closure capture biến — khi biến outer thay đổi, closure thấy giá trị mới hay cũ?

Xem đáp án

Đáp án: Mới. Closure capture by reference, không by value. Đổi outer → closure thấy ngay.

Q7

for (var i = 0; i < 3; i++) vs [0,1,2].forEach((i) {...}) — cái nào break được giữa chừng?

Xem đáp án

Đáp án: for được. forEach không — vì là method, body là function bình thường, không có loop context. Cần break → dùng for-in hoặc xs.takeWhile(...) kiểu functional.

Q8

assert(condition) chạy trong production AOT build không?

Xem đáp án

Đáp án: Không. Chỉ chạy debug/JIT mode (dart run, Flutter debug). Release AOT skip hoàn toàn — performance optimization. Đừng dùng assert cho validation user input.

18. Tổng kết

  • ✅ Function declaration: return type + name + params + body.
  • ✅ 3 loại parameter: positional (bắt buộc theo thứ tự), optional positional [ ], named { }.
  • required keyword cho named param non-nullable không default.
  • ✅ Arrow => cho function 1-expression.
  • ✅ Function là first-class: gán biến, truyền argument, return từ function.
  • ✅ Closure capture by reference scope ngoài.
  • ✅ Switch expression Dart 3 — exhaustive, trả value.
  • ✅ Loop: for, for-in, while, do-while, label break.
  • assert chỉ debug-only.
🧠 Mental model — chuẩn bị cho Flutter

Pattern bạn sẽ gặp ở Flutter mỗi chương:

Widget build(BuildContext context) {
  return Column(
    children: [
      Text('Hello'),
      ElevatedButton(
        onPressed: () => print('tap'),  // lambda
        child: const Text('Submit'),
      ),
    ],
  );
}

Mọi widget constructor dùng named param + required ngầm. onPressed nhận function (callback). Children là List của widget. — pattern này bạn đã quen sau chương 3.

19. Kết nối tới các chương khác

  • Chương 4 — Classes: constructor là function đặc biệt + named param + required.
  • Chương 7 — Async: async function là dạng function với keyword async.
  • Chương 8 — Patterns: switch expression mở rộng với pattern matching.
  • Flutter từ chương 2: mọi widget constructor dùng {required ...}.