Skip to main content

Kết Nối Ứng Dụng Flutter Với API Node.js

· 8 min read

Flutter, một UI toolkit đến từ Google, cho phép xây dựng ứng dụng mobile, web và desktop đẹp và native từ một codebase duy nhất. Node.js là một runtime environment cho JavaScript ở phía server, rất phổ biến để xây dựng các API nhanh và có khả năng mở rộng. Việc kết hợp sức mạnh của Flutter ở frontend và Node.js ở backend tạo nên một stack phát triển hiện đại và hiệu quả.

Bài viết này sẽ đi sâu vào cách ứng dụng Flutter của bạn có thể giao tiếp với API được xây dựng bằng Node.js, tập trung vào việc thực hiện các yêu cầu HTTP và xử lý dữ liệu.

Sơ đồ kiến trúc tổng thể

1. Thiết lập môi trường

Trước khi bắt đầu code, chúng ta cần đảm bảo môi trường phát triển đã sẵn sàng.

1.1 Chuẩn bị API Node.js

Để làm theo hướng dẫn này, bạn cần có một API Node.js đang chạy và có thể truy cập được. Nếu bạn chưa có, bạn có thể tạo một API cơ bản rất nhanh với Express.js. Dưới đây là ví dụ về một API Node.js đơn giản trả về danh sách người dùng và cho phép thêm người dùng mới:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors'); // Cần thiết nếu chạy Flutter app trên giả lập/thiết bị khác với server

const app = express();
const port = 3000;

// Middleware
app.use(cors());
app.use(bodyParser.json());

// Dữ liệu giả
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];

// GET request để lấy tất cả người dùng
app.get('/api/users', (req, res) => {
res.json(users);
});

// POST request để thêm người dùng mới
app.post('/api/users', (req, res) => {
const newUser = req.body;
if (!newUser || !newUser.name) {
return res.status(400).json({ message: 'Name is required' });
}
newUser.id = users.length + 1; // Tạo ID đơn giản
users.push(newUser);
res.status(201).json(newUser);
});

// Khởi chạy server
app.listen(port, () => {
console.log(`Node.js API listening at http://localhost:${port}`);
});

Lưu đoạn code trên vào một file (ví dụ: server.js), cài đặt các dependencies (npm install express body-parser cors) và chạy nó (node server.js). Đảm bảo API chạy trên một địa chỉ và cổng mà thiết bị Flutter của bạn có thể truy cập (ví dụ: http://10.0.2.2:3000 nếu chạy trên Android emulator và server trên localhost).

1.2 Thiết lập Project Flutter

Nếu bạn đã có project Flutter, hãy mở file pubspec.yaml. Nếu chưa, tạo project mới bằng flutter create your_app_name.

Chúng ta sẽ sử dụng package http để đơn giản. Thêm http vào phần dependencies trong pubspec.yaml:

dependencies:
flutter:
sdk: flutter

cupertino_icons: ^1.0.2

# Thêm package http tại đây
http: ^0.13.3 # Hoặc phiên bản mới nhất

dev_dependencies:
flutter_test:
sdk: flutter

flutter_lints: ^2.0.0

flutter:
uses-material-design: true

Sau khi thêm, chạy lệnh flutter pub get trong terminal của project Flutter để cài đặt package.

2. Thực hiện HTTP Request từ Flutter

Package http cung cấp các phương thức dễ sử dụng để thực hiện các yêu cầu HTTP phổ biến như GET, POST, PUT, DELETE.

Trước tiên, import package httpdart:convert (để xử lý JSON) vào file Dart của bạn:

import 'package:http/http.dart' as http;
import 'dart:convert';

Luồng HTTP Request và Response

2.1 GET Request: Lấy dữ liệu

Để lấy danh sách người dùng từ API Node.js ví dụ ở trên, bạn có thể tạo một hàm bất đồng bộ sử dụng http.get():

Future<List<dynamic>> fetchUsers() async {
final response = await http.get(Uri.parse('YOUR_NODEJS_API_URL/api/users'));

if (response.statusCode == 200) {
// Nếu server trả về mã status 200 OK, parse JSON
return jsonDecode(response.body); // response.body là chuỗi JSON
} else {
// Nếu không thành công, throw một exception.
throw Exception('Failed to load users');
}
}

Bạn cần thay thế 'YOUR_NODEJS_API_URL' bằng địa chỉ IP và cổng thực tế của API Node.js server của bạn.

2.2 POST Request: Gửi dữ liệu

Để thêm một người dùng mới, bạn sử dụng phương thức http.post(). Bạn cần cung cấp URL, headers (để thông báo định dạng dữ liệu gửi đi là JSON) và body (dữ liệu cần gửi, đã được encode sang chuỗi JSON).

Future<void> createUser(String name) async {
final response = await http.post(
Uri.parse('YOUR_NODEJS_API_URL/api/users'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{'name': name}),
);

if (response.statusCode == 201) {
// Nếu server trả về mã status 201 Created
print('User created successfully: ${response.body}');
} else {
// Nếu không thành công, throw một exception.
throw Exception('Failed to create user: ${response.statusCode}');
}
}

Sơ đồ luồng truyền và xử lý dữ liệu

3. Xử lý bất đồng bộ với Async/Await

Các thao tác mạng trong Flutter (và Dart) là bất đồng bộ. Điều này có nghĩa là chúng không chặn luồng chính của ứng dụng, giúp UI luôn mượt mà. Từ khóa asyncawait là cách hiện đại và dễ đọc để làm việc với các thao tác bất đồng bộ.

  • async: Được đặt trước một hàm để báo hiệu rằng hàm đó sẽ thực hiện các thao tác bất đồng bộ.
  • await: Được sử dụng bên trong một hàm async để chờ kết quả của một Future mà không chặn toàn bộ luồng thực thi. Khi gặp await, hàm sẽ tạm dừng, giải phóng CPU để làm việc khác, và sẽ tiếp tục chạy khi Future hoàn thành.

Các ví dụ fetchUserscreateUser ở trên đều sử dụng async/await để chờ kết quả từ http.gethttp.post.

4. Xử lý lỗi và phản hồi cho người dùng

Việc xử lý lỗi API là rất quan trọng để ứng dụng của bạn ổn định và cung cấp phản hồi rõ ràng cho người dùng. Luôn bọc các lệnh gọi API trong khối try-catch.

void _loadUsers() async {
try {
List<dynamic> usersList = await fetchUsers();
// Cập nhật UI với dữ liệu người dùng
print('Fetched ${usersList.length} users.');
// setState(() { _users = usersList; }); // Ví dụ cập nhật state
} catch (e) {
// Xử lý lỗi và thông báo cho người dùng
print('Error fetching users: $e');
// ScaffoldMessenger.of(context).showSnackBar(...); // Ví dụ hiển thị thông báo lỗi
}
}

void _addUser(String name) async {
try {
await createUser(name);
print('User added successfully.');
// Có thể fetch lại danh sách người dùng sau khi thêm thành công
// _loadUsers();
// ScaffoldMessenger.of(context).showSnackBar(...); // Ví dụ hiển thị thông báo thành công
} catch (e) {
print('Error adding user: $e');
// ScaffoldMessenger.of(context).showSnackBar(...); // Ví dụ hiển thị thông báo lỗi
}
}

Khi server trả về mã trạng thái lỗi (ví dụ: 400, 404, 500), phương thức http.get hoặc http.post sẽ không tự động throw lỗi. Bạn cần kiểm tra thuộc tính statusCode của response và throw exception một cách thủ công nếu cần xử lý trong khối catch.

5. Best Practices

  • Sử dụng biến môi trường: Không nên hardcode URL API trong code. Sử dụng các package như flutter_dotenv để quản lý biến môi trường.
  • Mô hình hóa dữ liệu: Thay vì làm việc trực tiếp với dynamic hoặc Map<String, dynamic>, hãy tạo các Dart class để mô hình hóa dữ liệu nhận được từ API (sử dụng json_serializable giúp tự động hóa).
  • Xử lý lỗi chi tiết: Phân loại các loại lỗi (lỗi mạng, lỗi server, lỗi parsing) và cung cấp thông báo cụ thể cho người dùng.
  • Sử dụng Dio thay vì http (Tùy chọn): Đối với các ứng dụng lớn hơn, package dio thường được ưa chuộng hơn http vì nó cung cấp nhiều tính năng hơn như interceptors, global configuration, form data, request cancellation, v.v.
  • Bảo mật: Đảm bảo kết nối API của bạn an toàn (sử dụng HTTPS). Xử lý xác thực và ủy quyền đúng cách.

Kết luận

Kết nối ứng dụng Flutter với API Node.js là một tác vụ phổ biến khi xây dựng các ứng dụng full-stack. Bằng cách hiểu rõ cách thực hiện HTTP requests, xử lý bất đồng bộ và quản lý lỗi trong Flutter, bạn có thể dễ dàng tích hợp ứng dụng mobile của mình với backend Node.js mạnh mẽ, mở ra nhiều khả năng phát triển ứng dụng.

Tài Liệu Tham Khảo