Hive
A lightweight, blazing fast key-value database written in pure Dart — perfect for storing complex objects with ease.
Hive is a lightweight, blazing fast key-value database written in pure Dart. It's inspired by Bitcask and designed to be simple, powerful, and intuitive .
✅ When to Use Hive
- Storing complex objects (custom classes)
- Large datasets that need to be queried and cached
- Offline-first applications
- When you need encrypted storage
- When you need real-time UI updates with ValueListenableBuilder
SharedPreferences
- Simple key-value pairs
- Primitive types only
- Small data (under 1MB)
- No encryption
- Platform-specific
- Slower for large data
Hive
- Key-value with complex objects
- Any Dart object (with type adapters)
- Large datasets
- AES-256 encryption
- Pure Dart (cross-platform)
- Blazing fast
💡 Which One to Choose?
- Use SharedPreferences – for simple app settings, user preferences, and small data
- Use Hive – for complex data, large datasets, offline-first apps, and encrypted storage
To use Hive, you need to add the dependency and initialize it.
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.0
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
// Initialize Hive
await Hive.initFlutter();
// Register adapters (for custom objects)
Hive.registerAdapter(PersonAdapter());
// Open a box
await Hive.openBox<Person>('peopleBox');
runApp(MyApp());
}
Hive works like a map. Here's how to use it for basic operations.
import 'package:hive_flutter/hive_flutter.dart';
class HiveService {
// Get a box reference
static Future<Box> getBox() async {
return await Hive.openBox('myBox');
}
// ----- WRITE DATA -----
static Future<void> putData(String key, dynamic value) async {
final box = await getBox();
await box.put(key, value);
}
static Future<void> putMultiple(Map<String, dynamic> data) async {
final box = await getBox();
await box.putAll(data);
}
static Future<void> addToList(dynamic value) async {
final box = await getBox();
await box.add(value);
}
// ----- READ DATA -----
static Future<dynamic> getData(String key) async {
final box = await getBox();
return box.get(key);
}
static Future<List<dynamic>> getAllValues() async {
final box = await getBox();
return box.values.toList();
}
static Future<Map<dynamic, dynamic>> getAll() async {
final box = await getBox();
return box.toMap();
}
// ----- DELETE DATA -----
static Future<void> deleteData(String key) async {
final box = await getBox();
await box.delete(key);
}
static Future<void> clearBox() async {
final box = await getBox();
await box.clear();
}
// ----- CHECK DATA -----
static Future<bool> containsKey(String key) async {
final box = await getBox();
return box.containsKey(key);
}
static Future<int> getBoxSize() async {
final box = await getBox();
return box.length;
}
}
Hive can store any Dart object using type adapters . You need to define a model class with annotations and generate the adapter.
import
'package:hive/hive.dart'
;
@HiveType
(typeId:
0
)
// 👈 Unique ID for this type
class
Person
extends
HiveObject {
@HiveField
(
0
)
final
String id;
@HiveField
(
1
)
final
String name;
@HiveField
(
2
)
final
int
age;
@HiveField
(
3
)
final
String? email;
Person
({
required
this
.id,
required
this
.name,
required
this
.age,
this
.email,
});
}
# Run this command to generate the adapter
flutter packages pub run build_runner build
# Or watch for changes
flutter packages pub run build_runner watch
import 'package:hive_flutter/hive_flutter.dart';
import 'models/person.dart';
import 'models/person.g.dart';
// Generated file
void main() async {
await Hive.initFlutter();
// Register the adapter
Hive.registerAdapter(PersonAdapter());
// Open a box with type
await Hive.openBox<Person>('peopleBox');
runApp(MyApp());
}
import 'package:hive_flutter/hive_flutter.dart';
import 'models/person.dart';
class PersonService {
static Future<Box<Person>> getBox() async {
return await Hive.openBox<Person>('peopleBox');
}
// Add a person
static Future<void> addPerson(Person person) async {
final box = await getBox();
await box.put(person.id, person);
}
// Get a person
static Future<Person?> getPerson(String id) async {
final box = await getBox();
return box.get(id);
}
// Get all people
static Future<List<Person>> getAllPeople() async {
final box = await getBox();
return box.values.toList();
}
// Delete a person
static Future<void> deletePerson(String id) async {
final box = await getBox();
await box.delete(id);
}
// Update a person
static Future<void> updatePerson(Person person) async {
final box = await getBox();
await box.put(person.id, person);
}
}
Hive integrates beautifully with Flutter through
ValueListenableBuilder
,
which rebuilds the UI when the data changes.
class PeopleListScreen extends StatefulWidget {
@override
State<PeopleListScreen> createState() => _PeopleListScreenState();
}
class _PeopleListScreenState extends State<PeopleListScreen> {
late Box<Person> _box;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _ageController = TextEditingController();
@override
void initState() {
super.initState();
_openBox();
}
Future<void> _openBox() async {
_box = await Hive.openBox<Person>('peopleBox');
setState(() {});
}
void _addPerson() {
final name = _nameController.text.trim();
final age = int.tryParse(_ageController.text.trim());
if (name.isNotEmpty && age != null) {
final person = Person(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
age: age,
);
_box.put(person.id, person);
_nameController.clear();
_ageController.clear();
}
}
void _deletePerson(String id) {
_box.delete(id);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('👥 People'), centerTitle: true),
body: Column(
children: [
// Input fields
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
flex: 3,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
hintText: 'Name',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 8),
Expanded(
flex: 2,
child: TextField(
controller: _ageController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Age',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: _addPerson,
child: Icon(Icons.add),
),
),
],
),
),
// List with real-time updates
Expanded(
child: _box == null
? Center(child: CircularProgressIndicator())
: ValueListenableBuilder(
valueListenable: _box.listenable(),
builder: (context, Box<Person> box, _) {
final people = box.values.toList();
if (people.isEmpty) {
return Center(child: Text('No people yet. Add one!'));
}
return ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
final person = people[index];
return ListTile(
leading: CircleAvatar(
child: Text(person.name[0].toUpperCase()),
),
title: Text(person.name),
subtitle: Text('Age: ${person.age}'),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _deletePerson(person.id),
),
);
},
);
},
),
),
],
),
);
}
}
Here's a complete to-do app using Hive for persistent storage with real-time UI updates.
import
'package:hive/hive.dart'
;
@HiveType
(typeId:
1
)
class
Todo
extends
HiveObject {
@HiveField
(
0
)
final
String id;
@HiveField
(
1
)
final
String title;
@HiveField
(
2
)
final
String? description;
@HiveField
(
3
)
final
bool
isCompleted;
@HiveField
(
4
)
final
DateTime createdAt;
Todo
({
required
this
.id,
required
this
.title,
this
.description,
this
.isCompleted =
false
,
DateTime
? createdAt,
}) :
this
.createdAt = createdAt ?? DateTime.now();
Todo
copyWith
({
String? id,
String? title,
String? description,
bool? isCompleted,
DateTime? createdAt,
}) {
return
Todo(
id: id ??
this
.id,
title: title ??
this
.title,
description: description ??
this
.description,
isCompleted: isCompleted ??
this
.isCompleted,
createdAt: createdAt ??
this
.createdAt,
);
}
}
class TodoScreen extends StatefulWidget {
@override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
late Box<Todo> _todoBox;
final TextEditingController _titleController = TextEditingController();
final TextEditingController _descController = TextEditingController();
@override
void initState() {
super.initState();
_openBox();
}
Future<void> _openBox() async {
_todoBox = await Hive.openBox<Todo>('todoBox');
setState(() {});
}
void _addTodo() {
final title = _titleController.text.trim();
if (title.isEmpty) return;
final todo = Todo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
description: _descController.text.trim(),
);
_todoBox.put(todo.id, todo);
_titleController.clear();
_descController.clear();
}
void _toggleTodo(String id) {
final todo = _todoBox.get(id);
if (todo != null) {
final updated = todo.copyWith(isCompleted: !todo.isCompleted);
_todoBox.put(id, updated);
}
}
void _deleteTodo(String id) {
_todoBox.delete(id);
}
void _showAddDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Add Todo'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(
hintText: 'Title...',
border: OutlineInputBorder(),
),
),
SizedBox(height: 8),
TextField(
controller: _descController,
decoration: InputDecoration(
hintText: 'Description (optional)',
border: OutlineInputBorder(),
),
maxLines: 3,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () {
_addTodo();
Navigator.pop(context);
},
child: Text('Add'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('✅ Todo List'), centerTitle: true),
body: _todoBox == null
? Center(child: CircularProgressIndicator())
: ValueListenableBuilder(
valueListenable: _todoBox.listenable(),
builder: (context, Box<Todo> box, _) {
final todos = box.values.toList();
if (todos.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.checklist,
size: 80,
color: Colors.grey.shade400,
),
SizedBox(height: 16),
Text(
'No todos yet!',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
Text(
'Tap the + button to add one',
style: TextStyle(color: Colors.grey.shade400),
),
],
),
);
}
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: Checkbox(
value: todo.isCompleted,
onChanged: (_) => _toggleTodo(todo.id),
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isCompleted
? TextDecoration.lineThrough
: null,
color: todo.isCompleted
? Colors.grey.shade600
: null,
),
),
subtitle: todo.description != null
? Text(
todo.description!,
style: TextStyle(
decoration: todo.isCompleted
? TextDecoration.lineThrough
: null,
color: todo.isCompleted
? Colors.grey.shade500
: null,
),
)
: null,
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _deleteTodo(todo.id),
),
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _showAddDialog,
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'models/todo.dart';
import 'models/todo.g.dart';
import 'screens/todo_screen.dart';
void main() async {
await Hive.initFlutter();
// Register adapters
Hive.registerAdapter(TodoAdapter());
// Open box
await Hive.openBox<Todo>('todoBox');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hive Todo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: TodoScreen(),
);
}
}
hive
,
hive_flutter
, and dev dependencies for code generation.
@HiveType
and
@HiveField
annotations.
build_runner
to generate the
.g.dart
file.
Hive.initFlutter()
and register the adapter.
Hive.openBox<T>('boxName')
to open a typed box.
ValueListenableBuilder
for real-time UI updates when data changes.
✅ Do's
- Use type adapters for all custom objects
- Use ValueListenableBuilder for real-time UI updates
- Use HiveObject for objects that need save/delete methods
- Group related data in different boxes for better organization
- Use encryption for sensitive data
-
Call
box.compact()periodically to reduce file size
❌ Don'ts
- Don't forget to register adapters before opening boxes
-
Don't change
typeIdafter data is stored - Don't store excessively large objects (consider SQLite)
- Don't use Hive for complex queries (use SQLite)
If you don't register the adapter, Hive won't know how to serialize/deserialize your custom objects.
Hive.registerAdapter(MyObjectAdapter());
If you change a
typeId
, Hive won't be able to read previously stored data.
Once you assign a
typeId
, never change it. If you need to add new fields, just add them.
Without
ValueListenableBuilder
, the UI won't update when the data changes.
ValueListenableBuilder(valueListenable: box.listenable(), builder: ...)
🎯 Key Takeaway
Hive is a powerful, fast, and easy-to-use NoSQL database for Flutter. It's perfect for storing complex objects with real-time UI updates. The type adapter system makes it type-safe, and ValueListenableBuilder provides automatic UI updates. Use Hive for offline-first apps and large datasets.