Khám Phá Dart Toàn Tập

Một tài liệu tương tác, chi tiết từ cơ bản đến nâng cao dành cho lập trình viên Flutter.

1. Giới thiệu về Dart

Dart là ngôn ngữ lập trình được Google phát triển, tối ưu hóa cho việc xây dựng giao diện người dùng (UI) nhanh, đẹp mắt trên nhiều nền tảng từ một cơ sở mã duy nhất. Nó là trái tim và linh hồn của Flutter.

Triết lý thiết kế

  • Tối ưu cho UI: Cung cấp các tính năng như Null Safety và lập trình bất đồng bộ để xây dựng UI phức tạp một cách an toàn và hiệu quả.
  • Năng suất cao: Cú pháp rõ ràng, quen thuộc và các công cụ mạnh mẽ như Hot Reload giúp đẩy nhanh quá trình phát triển.
  • Hiệu suất cao trên mọi nền tảng: Khả năng biên dịch JIT (phát triển) và AOT (phát hành) đảm bảo ứng dụng vừa linh hoạt vừa nhanh chóng.

2. Cài đặt môi trường

Bạn có thể bắt đầu với Dart theo nhiều cách. Đối với người mới, DartPad là lựa chọn nhanh nhất. Để phát triển Flutter chuyên nghiệp, bạn nên cài đặt VS Code hoặc Android Studio.

  • DartPad: Trình biên dịch online, không cần cài đặt. Lý tưởng để thử nghiệm nhanh các đoạn code. Truy cập dartpad.dev.
  • VS Code: Trình soạn thảo code nhẹ và mạnh mẽ. Cài extension `Dart` và `Flutter` từ Marketplace.
  • Android Studio: IDE đầy đủ tính năng từ Google. Cài plugin `Dart` và `Flutter` từ `Settings > Plugins`.

3. Cú pháp cơ bản

Nắm vững cú pháp cơ bản là nền tảng để viết bất kỳ chương trình Dart nào.

Biến và Kiểu dữ liệu

// Các kiểu dữ liệu cơ bản
String name = 'Flutter';
int version = 3;
double release = 3.16;
bool isAwesome = true;
num anyNumber = 10; // Có thể là int hoặc double

// Suy luận kiểu và kiểu động
var project = 'Dart'; // Dart tự suy luận là String
dynamic anything = 10; // Có thể thay đổi kiểu sau này
anything = 'Hello';

Hàm (Functions)

// Hàm cơ bản
int add(int a, int b) {
  return a + b;
}
// Cú pháp mũi tên cho hàm 1 dòng
int subtract(int a, int b) => a - b;

// Tham số được đặt tên (bắt buộc, tùy chọn, có giá trị mặc định)
void printDetails({required String name, int? age, String country = 'Vietnam'}) {
  print('Tên: $name, Tuổi: ${age ?? "Không rõ"}, Quốc gia: $country');
}

// Hàm ẩn danh (anonymous function)
var numbers = [1, 2, 3];
numbers.forEach((number) {
  print('Số: $number');
});

4. Lập trình Hướng đối tượng (OOP)

Dart là một ngôn ngữ hướng đối tượng thực thụ. Mọi thứ trong Dart là một đối tượng.

Class, Constructor, Kế thừa

class Vehicle {
  String brand;
  Vehicle(this.brand);
  void move() => print('$brand đang di chuyển.');
}

class Car extends Vehicle {
  int wheels = 4;
  Car(String brand) : super(brand); // Gọi constructor của cha
  @override
  void move() => print('Xe $brand đang lăn bánh.');
}

Getter, Setter và Thành viên `static`

class Rectangle {
  double left, top, width, height;
  Rectangle(this.left, this.top, this.width, this.height);

  // Getter
  double get right => left + width;

  // Setter
  set right(double value) => left = value - width;
}

class MathHelper {
  static const double PI = 3.14; // Thành viên static
  static double circleArea(double radius) => PI * radius * radius;
}
// Truy cập qua class, không cần tạo đối tượng
print(MathHelper.PI);

5. Null Safety

Tính năng giúp loại bỏ lỗi null. Mặc định, biến không thể là `null`.

// Các toán tử null-aware
String? guest;
String user = guest ?? 'Mặc định'; // ??
int? len = guest?.length; // ?.
guest ??= 'Tên mới'; // ??=
class MyClass {
  late String description; // Hứa sẽ khởi tạo sau
  void fetchData() {
    description = 'Dữ liệu đã được tải';
  }
}

6. Collections Nâng cao

Dart cung cấp các collection mạnh mẽ và các phương thức xử lý danh sách cực kỳ hữu ích.

var numbers = [1, 2, 3, 4, 5];
// map: biến đổi từng phần tử
var squared = numbers.map((n) => n * n).toList(); // [1, 4, 9, 16, 25]

// where: lọc các phần tử
var evens = numbers.where((n) => n.isEven).toList(); // [2, 4]

// reduce: gộp các phần tử thành 1 giá trị
var sum = numbers.reduce((value, element) => value + element); // 15

// Spread operator (...) và collection if/for
bool showExtra = true;
var items = [0, ...evens, if (showExtra) 99]; // [0, 2, 4, 99]

7. Xử lý Bất đồng bộ

Xử lý các tác vụ mất thời gian mà không làm "đóng băng" giao diện.

Future, async, await với Error Handling

Future fetchUserData() async {
  try {
    await Future.delayed(Duration(seconds: 2));
    throw 'Lỗi mạng'; // Giả lập lỗi
  } catch (e) {
    print('Đã xảy ra lỗi: $e');
    return 'Không thể tải dữ liệu';
  }
}

Stream: Dòng chảy dữ liệu

8. Dart Dành Cho Flutter

Đây là phần quan trọng nhất, kết nối kiến thức Dart với cách xây dựng ứng dụng Flutter.

StatelessWidget vs. StatefulWidget

`StatelessWidget` dành cho UI không thay đổi. `StatefulWidget` dành cho UI cần thay đổi dựa trên tương tác hoặc dữ liệu.

// StatelessWidget: không có trạng thái nội tại.
// Dùng cho các UI tĩnh.
import 'package:flutter/material.dart';

class MyButton extends StatelessWidget {
  final String text;
  const MyButton({super.key, required this.text});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text(text),
    );
  }
}
// StatefulWidget: có một đối tượng State đi kèm.
// Dùng khi UI cần được vẽ lại.
import 'package:flutter/material.dart';

class Counter extends StatefulWidget {
  const Counter({super.key});
  @override
  State createState() => _CounterState();
}

class _CounterState extends State {
  int _count = 0;
  void _increment() {
    setState(() { // Gọi setState để build() lại
      _count++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('Count: $_count');
  }
}

`build()` method và `BuildContext`

  • `build(BuildContext context)`: Phương thức cốt lõi của mọi widget. Nó mô tả widget sẽ trông như thế nào và được gọi mỗi khi widget cần được vẽ lại.
  • `BuildContext`: Là một "địa chỉ" cho widget trong cây widget (widget tree). Nó cho biết widget đang ở đâu và giúp truy cập các widget cha (ví dụ: `Theme.of(context)`).
  • Tầm quan trọng của `const`: Khi bạn khai báo một widget với `const`, Flutter sẽ không cần build lại nó, giúp tối ưu hiệu năng đáng kể. Hãy dùng `const` bất cứ khi nào có thể.

9. Các Chủ đề Nâng cao

Khám phá các tính năng mạnh mẽ của Dart để viết code linh hoạt và hiệu quả hơn.

Generics (``)

Generics cho phép bạn tạo ra các class và hàm có thể hoạt động với nhiều kiểu dữ liệu khác nhau một cách an toàn.

// Class Generic
class Cache {
  final T data;
  Cache(this.data);
}

// Hàm Generic
T getFirst(List items) {
  return items.first;
}

void main() {
  var stringCache = Cache('Hello');
  var intCache = Cache(123);
  
  var numbers = [1, 2, 3];
  print(getFirst(numbers)); // 1
}

Isolates: Lập trình đồng thời

Dart là ngôn ngữ đơn luồng (single-threaded). Để thực hiện các tác vụ nặng (như xử lý ảnh, tính toán phức tạp) mà không chặn luồng UI, Dart sử dụng `Isolates`. Mỗi Isolate có bộ nhớ riêng và giao tiếp với nhau qua message.

import 'dart:isolate';

// Hàm sẽ chạy trên một Isolate khác
void heavyTask(SendPort sendPort) {
  // Giả lập một tác vụ tính toán nặng
  int sum = 0;
  for (int i = 0; i < 1000000000; i++) {
    sum += i;
  }
  sendPort.send(sum); // Gửi kết quả về
}

Future main() async {
  final receivePort = ReceivePort();
  // Tạo một Isolate mới
  await Isolate.spawn(heavyTask, receivePort.sendPort);
  
  // Lắng nghe kết quả từ Isolate
  final result = await receivePort.first;
  print('Kết quả từ Isolate: $result');
}

10. Hệ sinh thái & pubspec

Để xây dựng ứng dụng thực tế, bạn cần quản lý các thư viện bên ngoài. Kho thư viện chính thức là pub.dev.

File `pubspec.yaml`

name: my_awesome_app
description: Một ứng dụng Flutter tuyệt vời.

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.1 
  provider: ^6.1.2

dev_dependencies:
  flutter_lints: ^3.0.0
  build_runner: ^2.4.9

11. Tối ưu & Best Practices

Viết code tốt không chỉ là làm cho nó chạy được, mà còn phải dễ đọc, dễ bảo trì và hiệu quả.

  • Ưu tiên `const`

    Luôn dùng `const` cho các widget và giá trị không đổi. Đây là cách tối ưu hiệu năng dễ dàng và hiệu quả nhất trong Flutter.

  • Sử dụng Linter

    Sử dụng các gói như `flutter_lints` để công cụ tự động phân tích và chỉ ra các vấn đề tiềm ẩn, giúp code của bạn sạch và nhất quán hơn.

  • Chia nhỏ Widgets

    Thay vì có một widget khổng lồ, hãy chia nó thành các widget con nhỏ hơn. Điều này giúp Flutter chỉ build lại những phần cần thiết khi trạng thái thay đổi.