RESTful API trong Khóa Học Flutter Level 3
RESTful API trong Khóa Học Flutter Level 3
Trong khóa học Flutter Level 3, việc tích hợp và làm việc với RESTful API là một trong những kỹ năng quan trọng mà học viên sẽ được học và thực hành. RESTful API là một giao thức phổ biến được sử dụng để giao tiếp giữa ứng dụng di động (Flutter) và backend server. Dưới đây là những nội dung chính liên quan đến RESTful API trong khóa học:
1. Tổng quan về RESTful API (Buổi 4)
- REST (Representational State Transfer) là một kiến trúc phần mềm được sử dụng để thiết kế các hệ thống mạng, đặc biệt là các ứng dụng web. RESTful API là các API tuân thủ các nguyên tắc của REST.
- Các phương thức HTTP phổ biến trong RESTful API:
- GET: Lấy dữ liệu từ server.
- POST: Gửi dữ liệu mới lên server.
- PUT: Cập nhật dữ liệu đã có trên server.
- DELETE: Xóa dữ liệu trên server.
- Cấu trúc API response: Thông thường, API response sẽ trả về dữ liệu dưới dạng JSON, bao gồm các trường như
status,message, vàdata.
Bài tập: Học viên sẽ thực hành gọi một API công khai (ví dụ: JSONPlaceholder) và hiển thị dữ liệu lên màn hình Flutter.
2. Giao tiếp với RESTful API bằng thư viện http (Buổi 4)
- Thư viện
httplà một thư viện đơn giản và phổ biến trong Flutter để thực hiện các yêu cầu HTTP. - Học viên sẽ học cách:
- Gửi các yêu cầu GET, POST, PUT, DELETE đến server.
- Xử lý response từ server và chuyển đổi JSON thành các đối tượng Dart.
- Xử lý lỗi khi gọi API, chẳng hạn như lỗi mạng hoặc lỗi từ server.
Bài tập: Học viên sẽ thực hành gọi API và hiển thị dữ liệu lên màn hình Flutter, đồng thời xử lý các trường hợp lỗi.
3. Sử dụng Dio để gọi API nâng cao (Buổi 5)
- Dio là một thư viện mạnh mẽ hơn so với
http, hỗ trợ nhiều tính năng nâng cao như interceptors, timeout, retry request, và hỗ trợ upload file. - Học viên sẽ học cách:
- Cấu hình
Diođể thêm các header tùy chỉnh (ví dụ: Authorization token). - Sử dụng interceptors để tự động thêm token vào mỗi request.
- Xử lý timeout và tự động retry request khi có lỗi mạng.
- Cấu hình
Bài tập: Học viên sẽ tích hợp Dio vào ứng dụng Flutter, thêm header cho request và xử lý các tình huống phức tạp hơn.
4. Authentication với API (JWT, OAuth2) (Buổi 6)
- JWT (JSON Web Token) là một chuẩn mã hóa để truyền thông tin an toàn giữa client và server. JWT thường được sử dụng để xác thực người dùng.
- OAuth2 là một giao thức ủy quyền, cho phép ứng dụng truy cập vào tài nguyên của người dùng mà không cần chia sẻ thông tin đăng nhập.
- Học viên sẽ học cách:
- Đăng nhập và đăng ký qua API.
- Lưu trữ token (JWT) trên thiết bị bằng
SharedPreferenceshoặcHive. - Tự động refresh token khi token hết hạn.
Bài tập: Học viên sẽ xây dựng màn hình đăng nhập và tích hợp API authentication vào ứng dụng Flutter.
5. Pagination và Infinite Scroll với API (Buổi 9)
- Pagination là kỹ thuật phân trang dữ liệu, giúp ứng dụng load dữ liệu theo từng phần thay vì load toàn bộ dữ liệu một lúc.
- Học viên sẽ học cách:
- Sử dụng các tham số như
offset,limit, hoặccursor-basedđể phân trang dữ liệu. - Triển khai infinite scroll trong Flutter bằng
ListView.builder. - Sử dụng
flutter_blocđể quản lý trạng thái khi load dữ liệu phân trang.
- Sử dụng các tham số như
Bài tập: Học viên sẽ thực hiện phân trang danh sách sản phẩm và tích hợp infinite scroll vào ứng dụng.
6. Bảo mật API & Xử lý lỗi nâng cao (Buổi 10)
- Bảo mật API là một yếu tố quan trọng khi phát triển ứng dụng di động. Học viên sẽ học các kỹ thuật bảo mật như:
- Sử dụng HTTPS để mã hóa dữ liệu truyền giữa client và server.
- SSL Pinning để ngăn chặn các cuộc tấn công Man-in-the-Middle (MITM).
- Xử lý lỗi nâng cao: Học viên sẽ học cách xử lý các lỗi API phức tạp và hiển thị thông báo lỗi thân thiện với người dùng.
Bài tập: Học viên sẽ cài đặt SSL Pinning trên ứng dụng Flutter và xử lý các lỗi API một cách chuyên nghiệp.
Kết quả
Qua các buổi học về RESTful API trong khóa học Flutter Level 3, học viên sẽ nắm vững cách tích hợp và làm việc với API trong ứng dụng Flutter. Từ việc gọi API cơ bản đến các kỹ thuật nâng cao như authentication, phân trang, và bảo mật, học viên sẽ có đủ kiến thức để xây dựng các ứng dụng di động tương tác với backend một cách hiệu quả và an toàn.
Kiến thức nền về REST API mà mọi lập trình viên Flutter cần
Trước khi viết dòng code Flutter nào, bạn cần hiểu rõ giao tiếp giữa app và server.
HTTP method và tính chất
| Method | Dùng cho | Idempotent? | Có body? |
|---|---|---|---|
GET | Đọc dữ liệu | ✅ Có | Không |
POST | Tạo mới | ❌ Không | Có |
PUT | Thay thế toàn bộ | ✅ Có | Có |
PATCH | Cập nhật một phẩn | ❌ Không | Có |
DELETE | Xoá | ✅ Có | Không |
Vì sao "idempotent" quan trọng: nếu request bị mất mạng và app gửi lại, GET/PUT/DELETE cho kết quả giống như gửi một lần. POST không — gửi lại có thể tạo hai bản ghi trùng. Đây là lý do thanh toán dùng idempotency key.
Mã trạng thái app phải xử lý
| Mã | Ý nghĩa | App nên làm gì |
|---|---|---|
| 200 | Thành công | Hiển thị dữ liệu |
| 201 | Đã tạo | Hiển thị bản ghi mới |
| 204 | Thành công, không có body | Không parse JSON |
| 400 | Dữ liệu gửi lên sai | Hiển thị lỗi validate |
| 401 | Chưa đăng nhập / token hết hạn | Chuyển về màn đăng nhập |
| 403 | Không có quyền | Thông báo không đủ quyền |
| 404 | Không tìm thấy | Hiển thị "không có dữ liệu" |
| 429 | Quá nhiều request | Chờ và thử lại (backoff) |
| 5xx | Lỗi server | Thông báo chung, cho phép thử lại |
⚠️ Lỗi phổ biến nhất trong code Flutter thực tế: chỉ kiểm tra
statusCode == 200. Khi API trả201hoặc204, app báo lỗi dù thực tất thành công. Hãy kiểm tra theo khoảng:statusCode >= 200 && statusCode < 300.
Cấu trúc một lớp API Service đúng chuẩn
// lib/services/api_client.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiException implements Exception {
final int? statusCode;
final String message;
ApiException(this.message, {this.statusCode});
@override
String toString() => message;
}
class ApiClient {
final String baseUrl;
final http.Client _client;
String? _token;
ApiClient({required this.baseUrl, http.Client? client})
: _client = client ?? http.Client();
void datToken(String? token) => _token = token;
Map<String, String> get _headers => {
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
if (_token != null) 'Authorization': 'Bearer $_token',
};
Future<dynamic> _xuLy(http.Response r) async {
// 204 hoặc body rỗng → không parse JSON
if (r.statusCode == 204 || r.body.isEmpty) return null;
// Decode UTF-8 tường minh — tiếng Việt sẽ lỗi nếu dùng r.body
final duLieu = jsonDecode(utf8.decode(r.bodyBytes));
if (r.statusCode >= 200 && r.statusCode < 300) return duLieu;
if (r.statusCode == 401) {
throw ApiException('Phiên đăng nhập đã hết hạn', statusCode: 401);
}
if (r.statusCode == 429) {
throw ApiException('Bạn thao tác quá nhanh, vui lòng thử lại sau',
statusCode: 429);
}
final thongDiep = duLieu is Map && duLieu['loi'] != null
? duLieu['loi'].toString()
: 'Lỗi không xác định (${r.statusCode})';
throw ApiException(thongDiep, statusCode: r.statusCode);
}
Future<dynamic> get(String path, {Map<String, String>? query}) async {
final uri = Uri.parse('$baseUrl$path').replace(queryParameters: query);
try {
return _xuLy(await _client.get(uri, headers: _headers)
.timeout(const Duration(seconds: 20)));
} on ApiException {
rethrow;
} catch (e) {
throw ApiException('Không kết nối được máy chủ. Kiểm tra mạng của bạn.');
}
}
Future<dynamic> post(String path, Map<String, dynamic> body) async {
try {
return _xuLy(await _client
.post(Uri.parse('$baseUrl$path'),
headers: _headers, body: jsonEncode(body))
.timeout(const Duration(seconds: 20)));
} on ApiException {
rethrow;
} catch (e) {
throw ApiException('Không kết nối được máy chủ. Kiểm tra mạng của bạn.');
}
}
}
Ba chi tiết kỹ thuật quan trọng trong đoạn code trên:
| Chi tiết | Vì sao |
|---|---|
utf8.decode(r.bodyBytes) | r.body đoán encoding từ header; nếu server trả sai, tiếng Việt bị lỗi font |
.timeout(...) | Không có timeout, request có thể treo vô hạn khi mạng yếu |
rethrow cho ApiException | Tránh bọc lỗi thành "không kết nối được" làm mất thông tin gốc |
Parse JSON an toàn
// lib/models/san_pham.dart
class SanPham {
final int id;
final String ten;
final double gia;
final String? hinhAnh;
const SanPham({
required this.id,
required this.ten,
required this.gia,
this.hinhAnh,
});
factory SanPham.fromJson(Map<String, dynamic> json) {
return SanPham(
id: (json['id'] as num).toInt(),
ten: json['ten'] as String? ?? 'Không có tên',
// Server có thể trả String hoặc num — xử lý cả hai
gia: json['gia'] is num
? (json['gia'] as num).toDouble()
: double.tryParse(json['gia'].toString()) ?? 0,
hinhAnh: json['hinh_anh'] as String?,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'ten': ten,
'gia': gia,
'hinh_anh': hinhAnh,
};
}
⚠️ Bẫy
as numvớitoInt(): JSON không phân biệtintvàdouble. Server có thể trả12hoặc12.0cho cùng một trường. Dùng(json['x'] as num).toInt()an toàn cho cả hai; dùngjson['x'] as intsẽ crash khi server trả12.0.
Bốn lỗi triển khai thường gặp
| Lỗi | Hậu quả | Cách sửa |
|---|---|---|
Gọi API trong build() | Gọi lại mỗi lần UI render → bão request | Gọi trong initState hoặc state manager |
| Không xử lý trạng thái loading/lỗi | App treo hoặc màn hình trắng | Dùng FutureBuilder hoặc AsyncValue |
| Hard-code API key trong app | Bị lộ khi giải nén APK | Proxy qua backend |
| Không cancel request khi rời màn hình | Ghi đè state sau khi dispose → crash | Kiểm tra mounted trước setState |
📚 Tài liệu khóa học
- Syllabus khóa học: Xem chi tiết tại đây
- Tài liệu khóa học: Cung cấp trong buổi học
- Đăng ký tham gia khóa học: http://zalo.me/0583587833
Hẹn gặp các bạn vào ngày khai giảng! 🚀