Using Hive for local storage in Flutter
By Charlin Joe · 19 September 2025
Hive is a lightweight, fast key-value store for Flutter built on top of binary serialization. It runs entirely in pure Dart (no native dependencies), which makes it easy to set up and cross-platform without any SQLite installation headaches.
When to use Hive
Hive is ideal when:
- You need fast read/write of non-relational data
- You're caching API responses or user-generated content offline
- You want reactivity without setting up a SQL schema
- You don't need complex queries (JOINs, aggregations)
For complex relational queries, prefer drift. For encrypted storage of secrets, prefer flutter_secure_storage.
Setup
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.0
Initialization
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(ProductAdapter());
Hive.registerAdapter(CartItemAdapter());
await Hive.openBox<Product>('products');
await Hive.openBox<CartItem>('cart');
runApp(const MyApp());
}
Defining data types
import 'package:hive/hive.dart';
part 'product.g.dart';
@HiveType(typeId: 0) // Unique ID across ALL types in your app
class Product extends HiveObject {
@HiveField(0) late String id;
@HiveField(1) late String name;
@HiveField(2) late double price;
@HiveField(3) late String imageUrl;
@HiveField(4) late DateTime updatedAt;
Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
required this.updatedAt,
});
}
Generate the adapter:
flutter pub run build_runner build --delete-conflicting-outputs
Reading and writing
final box = Hive.box<Product>('products');
// Write
await box.put('prod-1', Product(
id: 'prod-1',
name: 'Blue Widget',
price: 9.99,
imageUrl: 'https://...',
updatedAt: DateTime.now(),
));
// Bulk write
await box.putAll({'prod-2': product2, 'prod-3': product3});
// Read
final product = box.get('prod-1'); // null if not found
final allProducts = box.values.toList();
// Delete
await box.delete('prod-1');
await box.clear(); // Delete all
Auto-increment keys
await box.add(product); // Returns the integer key
await box.addAll([p1, p2, p3]);
final first = box.getAt(0);
await box.deleteAt(0);
HiveObject convenience methods
When your type extends HiveObject, you get save/delete directly on the object:
final product = box.get('prod-1')!;
product.price = 8.99;
await product.save(); // Updates in box automatically
await product.delete();
Reactive UI with ValueListenableBuilder
class ProductListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box<Product>>(
valueListenable: Hive.box<Product>('products').listenable(),
builder: (context, box, _) {
final products = box.values.toList();
if (products.isEmpty) {
return const Center(child: Text('No products'));
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductCard(product: products[i]),
);
},
);
}
}
Listen to specific keys only:
Hive.box<Product>('products').listenable(keys: ['prod-1', 'prod-2'])
Encrypted boxes
Hive supports AES-256 encryption. Store the encryption key in flutter_secure_storage:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'dart:convert';
const storage = FlutterSecureStorage();
Future<Box> openEncryptedBox(String name) async {
var keyString = await storage.read(key: 'hive_key');
late List<int> encryptionKey;
if (keyString == null) {
encryptionKey = Hive.generateSecureKey();
await storage.write(key: 'hive_key', value: base64Url.encode(encryptionKey));
} else {
encryptionKey = base64Url.decode(keyString);
}
return Hive.openBox(name, encryptionCipher: HiveAesCipher(encryptionKey));
}
Lazy boxes for large datasets
Regular boxes load all values into memory at open. Use LazyBox for large datasets:
final lazyBox = await Hive.openLazyBox<Product>('large_catalog');
final product = await lazyBox.get('prod-1'); // Reads from disk on demand
Testing
setUp(() async {
Hive.init(Directory.systemTemp.path);
Hive.registerAdapter(ProductAdapter());
await Hive.openBox<Product>('products');
});
tearDown(() async {
await Hive.deleteFromDisk();
});
Common pitfalls
Duplicate typeId. Every @HiveType(typeId: N) must be unique across your entire app. Duplicate IDs cause data corruption. Keep a comment somewhere listing all used IDs.
Changing field numbers. Once a type is written to disk, changing @HiveField(N) numbers makes existing data unreadable. You can add new fields with new numbers, but never change existing ones.
Not closing boxes before re-opening. Opening the same box twice without closing throws an error. In tests, Hive.deleteFromDisk() in tearDown gives you a clean slate.
Sign in to like, dislike, or report.