Nguyễn Đình Cường
0 comment
09 Nov, 2026
Không phải dữ liệu nào cũng cần gửi lên máy chủ. Cài đặt giao diện, trạng thái đăng nhập hay danh sách ghi chú đều nên được lưu ngay trên điện thoại. Flutter có nhiều cách làm việc này, và hai lựa chọn phổ biến nhất là shared_preferences và SQLite.
| Loại dữ liệu | Gợi ý |
|---|---|
| Cài đặt nhỏ: chế độ tối, ngôn ngữ, đã xem hướng dẫn | shared_preferences |
| Danh sách bản ghi có cấu trúc, cần tìm kiếm, sắp xếp | SQLite (sqflite, drift) |
| Token, mật khẩu, thông tin nhạy cảm | flutter_secure_storage |
| Tệp lớn như ảnh, tài liệu | Lưu vào thư mục ứng dụng qua path_provider |
Package này lưu các giá trị đơn giản như bool, int, double, String và danh sách chuỗi. Thao tác đều bất đồng bộ.
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('dark_mode', value);
}
Future<bool> loadDarkMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('dark_mode') ?? false;
}
Hãy dùng toán tử ?? để cung cấp giá trị mặc định cho lần chạy đầu tiên. Đừng lưu dữ liệu lớn hay nhạy cảm vào đây, vì nó không được thiết kế cho mục đích đó và không được mã hóa.
Khi dữ liệu có nhiều bản ghi và quan hệ, cơ sở dữ liệu SQLite nhúng sẵn trong điện thoại là lựa chọn phù hợp. Package sqflite cho phép chạy câu lệnh SQL trực tiếp.
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> openNotesDb() async {
final path = join(await getDatabasesPath(), 'notes.db');
return openDatabase(
path,
version: 1,
onCreate: (db, version) {
return db.execute(
'CREATE TABLE notes(id INTEGER PRIMARY KEY AUTOINCREMENT, '
'title TEXT NOT NULL, content TEXT)',
);
},
);
}
Future<int> addNote(Database db, String title, String content) {
return db.insert('notes', {'title': title, 'content': content});
}
Future<List<Map<String, Object?>>> getNotes(Database db) {
return db.query('notes', orderBy: 'id DESC');
}
Luôn dùng tham số (whereArgs) khi lọc dữ liệu thay vì nối chuỗi, để tránh lỗi và nguy cơ SQL injection:
await db.delete('notes', where: 'id = ?', whereArgs: [noteId]);
Khi phát hành phiên bản mới có thêm cột hoặc bảng, bạn tăng version và xử lý trong onUpgrade. Nếu bỏ qua bước này, người dùng cập nhật ứng dụng có thể bị lỗi vì cơ sở dữ liệu cũ thiếu cột. Hãy thử nghiệm kỹ việc nâng cấp từ bản cũ trước khi phát hành.
openDatabase(
path,
version: 2,
onCreate: _createV2,
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE notes ADD COLUMN pinned INTEGER DEFAULT 0');
}
},
);
Cứ nhớ nguyên tắc đơn giản: giá trị nhỏ dùng shared_preferences, dữ liệu có cấu trúc dùng SQLite, bí mật dùng kho lưu trữ an toàn. Đây là nền tảng để làm những ứng dụng hoạt động được cả khi mất mạng.
Đừng rải lời gọi SharedPreferences khắp ứng dụng. Hãy tạo một lớp riêng với các phương thức mang tên có nghĩa nghiệp vụ. Khi sau này cần đổi sang kho lưu trữ khác, bạn chỉ sửa một chỗ:
class SettingsStore {
static const _kDark = 'dark_mode';
static const _kLang = 'language';
Future<bool> isDarkMode() async {
final p = await SharedPreferences.getInstance();
return p.getBool(_kDark) ?? false;
}
Future<void> setDarkMode(bool v) async {
final p = await SharedPreferences.getInstance();
await p.setBool(_kDark, v);
}
Future<String> language() async {
final p = await SharedPreferences.getInstance();
return p.getString(_kLang) ?? 'vi';
}
}
Việc dùng hằng số cho tên khóa tránh lỗi gõ sai chuỗi, một nguồn bug rất khó thấy. Với SQLite, áp dụng ý tưởng tương tự: tạo lớp NoteRepository chứa các hàm add, getAll, delete để phần còn lại của ứng dụng không cần biết đến câu lệnh SQL. Thêm vào đó, hãy xử lý trường hợp dữ liệu cũ bị hỏng hoặc thiếu bằng giá trị mặc định thay vì để ứng dụng sập.
Dữ liệu cục bộ tiện nhưng có vài điểm cần cân nhắc. Thứ nhất, dữ liệu không đồng bộ giữa các thiết bị; nếu người dùng đổi máy, họ mất dữ liệu trừ khi bạn có sao lưu hoặc đồng bộ với máy chủ. Thứ hai, người dùng có thể xóa dữ liệu ứng dụng bất kỳ lúc nào, nên mã phải chịu được tình huống cơ sở dữ liệu trống. Thứ ba, hệ điều hành có thể tự sao lưu dữ liệu ứng dụng vào đám mây theo cài đặt; hãy kiểm tra xem bạn có muốn dữ liệu nhạy cảm bị sao lưu hay không và cấu hình loại trừ nếu cần.
Cuối cùng, đừng dùng bộ nhớ cục bộ như nơi duy nhất cho dữ liệu quan trọng của người dùng. Hãy coi nó là bộ nhớ đệm hoặc bản sao làm việc, còn nguồn chính nằm ở máy chủ khi nội dung có giá trị lâu dài.
Nếu bạn muốn học cách thiết kế ứng dụng lưu dữ liệu cục bộ rồi đồng bộ với máy chủ, hãy xem thêm tại codebem.com.
Nguyễn Đình Cường
0 comment