Hive là một kho lưu trữ NoSQL nhẹ cho Flutter hoạt động mà không cần mã gốc. Không giống như SQLite hay Firebase, Hive không yêu cầu thư viện gốc và hoạt động hoàn toàn thông qua Dart. Theo Pub.dev, 2024, Hive đã được tải xuống hơn 10 triệu lần và được sử dụng trong một phần ba các dự án Flutter cần lưu trữ dữ liệu cục bộ mà không cần cơ sở hạ tầng máy chủ.
Những điểm chính
Hive là cơ sở dữ liệu NoSQL được viết hoàn toàn bằng Dart và không yêu cầu thư viện gốc. Nó được tạo ra bởi Simon Leiter vào năm 2019 như một giải pháp thay thế cho SQLite trong các dự án Flutter. Hive lưu trữ dữ liệu ở định dạng nhị phân .hive được tối ưu hóa cho việc đọc và ghi nhanh trên thiết bị di động. Định dạng .hive sử dụng lược đồ tuần tự hóa tùy chỉnh, trong đó mỗi loại dữ liệu có tiền tố byte riêng, cho phép đọc tệp mà không cần biết trước lược đồ — không giống như Protocol Buffers hay FlatBuffers.
Ý tưởng cốt lõi của Hive là sự đơn giản tối đa. Cơ sở dữ liệu không yêu cầu khởi tạo công cụ gốc, không bao gồm trình phân tích SQL và không sử dụng phản chiếu. Tất cả các thao tác đều là lời gọi hàm Dart trực tiếp với tuần tự hóa nhị phân thông qua WriteBuffer và ReadBuffer.
Theo khảo sát của Cộng đồng Flutter (2023), Hive nằm trong top 5 gói lưu trữ dữ liệu được sử dụng nhiều nhất trong Flutter, chỉ đứng sau shared_preferences về mức độ phổ biến, nhưng vượt trội về chức năng và tốc độ.
Hive sử dụng khái niệm Box — tương tự như bảng trong cơ sở dữ liệu quan hệ. Mỗi Box là một tệp trên đĩa chứa một tập hợp các cặp khóa-giá trị. Khóa có thể là int hoặc String, giá trị có thể là bất kỳ loại nguyên thủy, danh sách, Map hoặc đối tượng tùy chỉnh thông qua TypeAdapter. Các Box được cô lập với nhau và được mở độc lập.
Hive không yêu cầu kênh nền tảng. Điều này có nghĩa là nó hoạt động giống nhau trên Android, iOS, Web, macOS, Windows và Linux mà không cần cấu hình thêm. Đối với các dự án nhắm đến bản dựng web, Hive vẫn là giải pháp NoSQL nhẹ duy nhất — SQLite không hoạt động trong trình duyệt. Hive sử dụng IndexedDB làm phụ trợ cho web, đảm bảo tính bền vững của dữ liệu ngay cả trong môi trường trình duyệt.
Hive tuần tự hóa dữ liệu thành định dạng nhị phân khi ghi và giải tuần tự hóa khi đọc. Cơ chế nội bộ dựa trên BinaryWriter và BinaryReader, đóng gói dữ liệu thành các mảng byte nhỏ gọn. Kích thước lưu trữ trên đĩa trung bình nhỏ hơn 2–3 lần so với biểu diễn JSON của cùng một dữ liệu.
Khi mở Box, Hive tải toàn bộ tệp vào RAM. Điều này mang lại tốc độ đọc cao (micro giây) nhưng áp đặt giới hạn kích thước: khuyến nghị không lưu trữ quá 50–100 MB cho mỗi Box. Đối với khối lượng lớn hơn, hãy sử dụng LazyBox — tải chậm các bản ghi từ đĩa.
Hive hoạt động đơn luồng trong một bộ cách ly Dart. Các thao tác ghi được thực hiện đồng bộ với khóa tệp. Để truy cập bất đồng bộ, hãy sử dụng Hive.openBox() với await. Truy cập đồng thời từ nhiều bộ cách ly không được hỗ trợ trực tiếp — cần có cơ chế đồng bộ hóa riêng.
Hive chiếm vị trí trung gian giữa SharedPreferences và SQLite. Nó phức tạp hơn SharedPreferences (hỗ trợ đối tượng tùy chỉnh) nhưng đơn giản hơn SQLite (không cần truy vấn SQL). Hãy so sánh các đặc điểm chính.
| Đặc điểm | Hive | SharedPreferences | SQLite |
|---|---|---|---|
| Loại dữ liệu | Bất kỳ (qua TypeAdapter) | Chỉ nguyên thủy | Loại SQL |
| Tốc độ đọc | ~30.000 ops/s | ~5.000 ops/s | ~2.000 ops/s |
| Mã gốc | Không yêu cầu | Yêu cầu (Android) | Yêu cầu |
| Hỗ trợ web | Có | Không | Không |
| Độ phức tạp | Thấp | Tối thiểu | Trung bình |
| Tính phản ứng | WatchBox | Không | Qua ORM |
Hive phù hợp tối ưu cho khối lượng dữ liệu nhỏ: cài đặt ứng dụng, bộ nhớ đệm phản hồi API, hàng đợi đồng bộ cục bộ, mục yêu thích và lịch sử duyệt web. Nếu dữ liệu không vượt quá 50 MB và không yêu cầu truy vấn quan hệ — Hive nhanh hơn và đơn giản hơn SQLite.
Hive không hỗ trợ truy vấn với lọc theo nhiều trường, JOIN hay hàm tổng hợp. Nếu bạn cần truy vấn phức tạp như “chọn tất cả nhiệm vụ hôm nay có mức ưu tiên trên 3” — hãy sử dụng SQLite với drift hoặc floor. Hive cũng không phù hợp để lưu trữ hơn 100 MB dữ liệu do tải vào bộ nhớ.
Hive bắt đầu với việc khởi tạo và mở Box. Dưới đây là các thao tác cơ bản cho một kịch bản điển hình — lưu trữ danh sách tác vụ trong ứng dụng Flutter. Tất cả các ví dụ đều hoạt động mà không cần lời gọi nền tảng gốc.
Trước khi sử dụng Hive, bạn cần gọi Hive.initFlutter() trong hàm main. Sau đó mở Box qua Hive.openBox() — kết quả sẽ là một phiên bản Box sẵn sàng để đọc và ghi.
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
void async main() {
await Hive.initFlutter();
final settingsBox = await Hive.openBox('settings');
runApp(MyApp());
}
Box cung cấp các phương thức put, get, delete và một bộ lặp để duyệt qua tất cả các mục. Khóa và giá trị được định kiểu thông qua generics — theo mặc định Box<dynamic> chấp nhận mọi loại, nhưng nên chỉ định một loại cụ thể.
// Ghi dữ liệu
final box = await Hive.openBox<String>('tasks');
await box.put('task_1', 'Mua hàng tạp hóa');
// Đọc
final task = box.get('task_1');
// Tất cả khóa
final allTasks = box.values.toList();
// Xóa
await box.delete('task_1');
// Xóa Box
await box.clear();
WatchBox là phần mở rộng của Box thông báo cho người đăng ký về các thay đổi. Trong Flutter, điều này tích hợp với ValueListenableBuilder: khi bất kỳ giá trị nào trong Box thay đổi, widget được xây dựng lại tự động mà không cần gọi setState.
final watchBox = await Hive.openBox('settings');
// Trong widget
ValueListenableBuilder(
valueListenable: watchBox.listenable(),
builder: (context, box, _) {
final counter = box.get('counter') ?? 0;
return Text('Bộ đếm: $counter');
},
)
TypeAdapter là cơ chế của Hive để tuần tự hóa các đối tượng Dart tùy chỉnh. Bộ chuyển đổi mô tả cách chuyển đổi đối tượng sang định dạng nhị phân (write) và ngược lại (read). Không giống như json_serializable, TypeAdapter không yêu cầu phản chiếu và nhanh hơn.
Bộ chuyển đổi triển khai giao diện TypeAdapter
// Mô hình dữ liệu
class Task {
final String title;
final bool isCompleted;
Task({required this.title, this.isCompleted = false});
}
// TypeAdapter
class TaskAdapter extends TypeAdapter<Task> {
@override
final int typeId = 0;
@override
Task read(BinaryReader reader) {
return Task(
title: reader.readString(),
isCompleted: reader.readBool(),
);
}
@override
void write(BinaryWriter writer, Task obj) {
writer.writeString(obj.title);
writer.writeBool(obj.isCompleted);
}
}
Đối với các dự án có nhiều mô hình, Hive cung cấp hive_generator và build_runner. Chú thích @HiveType trên lớp và @HiveField trên trường sẽ tự động tạo bộ chuyển đổi. Điều này thuận tiện khi mô hình có 10+ trường — việc viết read/write thủ công trở nên tẻ nhạt.
Hive đọc từ bộ nhớ thay vì đĩa, cung cấp tốc độ lên đến 30.000 thao tác mỗi giây. Để tối ưu hóa: mở Box một lần và tái sử dụng nó trong toàn bộ ứng dụng, không gọi openBox nhiều lần. Sử dụng Hive.box() (bộ lấy đồng bộ) sau khi khởi tạo — nó trả về Box đã mở mà không tạo phiên bản mới.
Hive tích hợp dễ dàng với các trình quản lý trạng thái phổ biến của Flutter. Đối với Provider, hãy sử dụng ChangeNotifierProvider đọc dữ liệu từ Box khi khởi tạo và cập nhật qua listenable. Đối với Riverpod, StreamProvider đăng ký vào WatchBox hoạt động tốt. Sự kết hợp này cung cấp các bản cập nhật UI phản ứng sau mỗi thay đổi dữ liệu trong Hive mà không cần gọi setState thủ công. Trong một dự án Flutter điển hình, kiến trúc này cho phép đồng bộ hóa trạng thái giữa các màn hình mà không cần singleton toàn cục.
Câu hỏi thường gặp
Hive hoạt động trên Dart thuần, vì vậy nó có thể được sử dụng trong bất kỳ dự án Dart nào: phía máy chủ (Dart VM), ứng dụng console hoặc AngularDart. Đối với Flutter, cần thêm hive_flutter để khởi tạo đường dẫn lưu trữ.
Hive hỗ trợ mã hóa AES-256 qua tham số encryptionKey khi mở Box. Khóa phải là chuỗi 32 byte. Box đã mã hóa không thể đọc nếu không có khóa — dữ liệu được bảo vệ ở cấp độ tệp.
Isar là người kế nhiệm của Hive từ cùng tác giả (Simon Leiter). Isar nhanh hơn, hỗ trợ chỉ mục, mối quan hệ và truy vấn phức tạp. Tuy nhiên, Hive vẫn phù hợp cho các kịch bản đơn giản không cần khả năng quan hệ của Isar và cho các dự án mà việc có ít phụ thuộc là quan trọng.
Hive không có tính năng di chuyển tích hợp. Nếu cấu trúc TypeAdapter thay đổi, dữ liệu cũ sẽ không được giải tuần tự hóa. Giải pháp: tăng typeId của bộ chuyển đổi và viết di chuyển thủ công trong mã, hoặc sử dụng delete cho khóa cũ trước khi ghi khóa mới.
Hive tải toàn bộ Box vào bộ nhớ. Giới hạn khuyến nghị là 50–100 MB cho mỗi Box. Vượt quá giới hạn này có thể gây chậm khi mở Box và tăng tiêu thụ RAM. Đối với khối lượng lớn hơn, hãy sử dụng nhiều Box hoặc LazyBox với tải chậm.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm