Hive is a lightweight, high-performance NoSQL database written in pure Dart. It’s specifically designed for Flutter applications, making it an excellent choice for local data persistence. Compared to other database solutions like SQLite or SharedPreferences, Hive offers several advantages:
- Speed: Hive is extremely fast, with benchmarks showing it can be up to 2x faster than most alternatives
- Ease of use: Simple API that requires minimal setup code
- Cross-platform: Works seamlessly on all platforms supported by Flutter
- Type safety: Supports strongly typed objects through code generation
- Encryption: Data can be encrypted for additional security
- No native dependencies: Written in pure Dart, avoiding platform-specific code
This guide will walk you through the complete implementation of Hive in a Flutter application, from initial setup to advanced usage patterns.
Table of Contents
Setting Up Hive
Step 1: Add Dependencies
First, add the necessary dependencies to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
hive_generator: ^2.0.0
build_runner: ^2.3.3
The hive
package is the core database, while hive_flutter
provides Flutter-specific functionality. The hive_generator
and build_runner
packages are used for code generation to create type adapters.
Step 2: Initialize Hive
In your main.dart
file, initialize Hive before running your app:
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
// Initialize Hive
await Hive.initFlutter();
// Then you can open your boxes
await Hive.openBox('settingsBox');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hive Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
The initFlutter()
method initializes Hive with a default path in your app’s documents directory. You can then open boxes, which are similar to tables in traditional databases.
Defining Data Models
Step 1: Create Model Classes
To store structured data, you’ll need to create model classes. For example, let’s create a Task
model:
import 'package:hive/hive.dart';
part 'task.g.dart'; // This file will be generated
@HiveType(typeId: 0)
class Task extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String title;
@HiveField(2)
late String description;
@HiveField(3)
late bool isCompleted;
@HiveField(4)
late DateTime createdAt;
// Default constructor
Task({
required this.id,
required this.title,
required this.description,
this.isCompleted = false,
required this.createdAt,
});
}
In this example:
@HiveType(typeId: 0)
marks the class as a Hive model with a unique ID@HiveField(n)
marks properties for storage, wheren
is a unique identifier for each field- Extending
HiveObject
gives the class additional functionality likesave()
anddelete()
Step 2: Generate Type Adapters
Run the following command to generate the necessary code for your models:
flutter pub run build_runner build
This will generate a file called task.g.dart
with the type adapters Hive needs to serialize and deserialize your objects.
Step 3: Register Type Adapters
Before using your model classes, register their type adapters in your initialization code:
void main() async {
await Hive.initFlutter();
// Register adapters
Hive.registerAdapter(TaskAdapter());
// Open boxes
await Hive.openBox<Task>('tasksBox');
runApp(MyApp());
}
CRUD Operations
Creating and Opening Boxes
Boxes are the main storage unit in Hive. They’re similar to tables in SQL databases:
// Open or create a box
final box = await Hive.openBox<Task>('tasksBox');
// You can also open a box without specifying a type, but you'll lose type safety
final dynamicBox = await Hive.openBox('settingsBox');
Create (Insert) Operations
// Create a new task
final newTask = Task(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: 'Learn Hive',
description: 'Complete the Hive tutorial',
createdAt: DateTime.now(),
);
// Add to box (auto-generates key)
final key = await box.add(newTask);
print('Task added with key: $key');
// Or provide a specific key
await box.put('task_1', newTask);
Read Operations
// Get a value by key
final task = box.get('task_1');
// Get all values
final allTasks = box.values.toList();
// Get the first value
final firstTask = box.getAt(0);
// Check if a key exists
final exists = box.containsKey('task_1');
// Get keys
final keys = box.keys.toList();
Update Operations
// Update an existing task
final taskToUpdate = box.get('task_1');
if (taskToUpdate != null) {
taskToUpdate.isCompleted = true;
await taskToUpdate.save(); // If using HiveObject
}
// Or update directly
await box.put('task_1', Task(
id: 'task_1',
title: 'Updated title',
description: 'Updated description',
isCompleted: true,
createdAt: DateTime.now(),
));
Delete Operations
// Delete by key
await box.delete('task_1');
// Delete at index
await box.deleteAt(0);
// Delete multiple entries
await box.deleteAll(['task_1', 'task_2']);
// Clear the entire box
await box.clear();
Advanced Usage
Lazy Boxes
For better performance with large data sets, use lazy boxes:
final lazyBox = await Hive.openLazyBox<Task>('tasksLazyBox');
// Values must be loaded asynchronously
final task = await lazyBox.get('task_1');
Filtering and Querying
Hive doesn’t provide SQL-like queries, but you can filter data using Dart’s collection methods:
// Get all completed tasks
final completedTasks = box.values.where((task) => task.isCompleted).toList();
// Get tasks created today
final today = DateTime.now();
final todayTasks = box.values.where((task) =>
task.createdAt.day == today.day &&
task.createdAt.month == today.month &&
task.createdAt.year == today.year
).toList();
// Sort tasks by creation date
final sortedTasks = box.values.toList()
..sort((a, b) => a.createdAt.compareTo(b.createdAt));
Listening to Changes
You can react to changes in a box:
box.listenable().addListener(() {
print('Box changed');
});
// Or with Flutter widgets
class TaskList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: Hive.box<Task>('tasksBox').listenable(),
builder: (context, Box<Task> box, _) {
return ListView.builder(
itemCount: box.length,
itemBuilder: (context, index) {
final task = box.getAt(index);
return ListTile(
title: Text(task?.title ?? ''),
subtitle: Text(task?.description ?? ''),
trailing: Checkbox(
value: task?.isCompleted ?? false,
onChanged: (val) {
if (task != null) {
task.isCompleted = val ?? false;
task.save();
}
},
),
);
},
);
},
);
}
}
Encryption
For sensitive data, you can encrypt your boxes:
// Add hive_flutter encryption dependency in pubspec.yaml
// dependencies:
// encrypted_hive: ^1.0.0
import 'package:hive/hive.dart';
import 'package:encrypted_hive/encrypted_hive.dart';
void main() async {
await Hive.initFlutter();
// Generate a secure key
final encryptionKey = Hive.generateSecureKey();
// Open an encrypted box
final encryptedBox = await Hive.openBox<Task>(
'tasksEncryptedBox',
encryptionCipher: HiveAesCipher(encryptionKey),
);
}
Best Practices
1. Box Management
Keep track of your open boxes and close them when they’re no longer needed:
// In a service class
class HiveService {
static final Map<String, Box> _boxes = {};
static Future<Box<T>> openBox<T>(String boxName) async {
if (_boxes.containsKey(boxName)) {
return _boxes[boxName] as Box<T>;
}
final box = await Hive.openBox<T>(boxName);
_boxes[boxName] = box;
return box;
}
static Future<void> closeBox(String boxName) async {
if (_boxes.containsKey(boxName)) {
final box = _boxes[boxName]!;
await box.close();
_boxes.remove(boxName);
}
}
static Future<void> closeAllBoxes() async {
for (final box in _boxes.values) {
await box.close();
}
_boxes.clear();
}
}
// Usage
final tasksBox = await HiveService.openBox<Task>('tasksBox');
2. Type ID Management
Keep track of your type IDs to avoid conflicts:
// types.dart
abstract class HiveTypes {
static const int task = 0;
static const int user = 1;
static const int category = 2;
// Add more as needed
}
// task.dart
@HiveType(typeId: HiveTypes.task)
class Task extends HiveObject { ... }
3. Repository Pattern
Implement the repository pattern to abstract database operations:
abstract class TaskRepository {
Future<List<Task>> getAllTasks();
Future<Task?> getTaskById(String id);
Future<void> addTask(Task task);
Future<void> updateTask(Task task);
Future<void> deleteTask(String id);
}
class HiveTaskRepository implements TaskRepository {
late Box<Task> _box;
Future<void> initialize() async {
_box = await Hive.openBox<Task>('tasksBox');
}
@override
Future<List<Task>> getAllTasks() async {
return _box.values.toList();
}
@override
Future<Task?> getTaskById(String id) async {
return _box.get(id);
}
@override
Future<void> addTask(Task task) async {
await _box.put(task.id, task);
}
@override
Future<void> updateTask(Task task) async {
await _box.put(task.id, task);
}
@override
Future<void> deleteTask(String id) async {
await _box.delete(id);
}
}
Complete Example
Here’s a complete example of a task management app using Hive:
1. Task Model (lib/models/task.dart
)
import 'package:hive/hive.dart';
part 'task.g.dart';
@HiveType(typeId: 0)
class Task extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String title;
@HiveField(2)
String description;
@HiveField(3)
bool isCompleted;
@HiveField(4)
DateTime createdAt;
Task({
required this.id,
required this.title,
required this.description,
this.isCompleted = false,
required this.createdAt,
});
}
2. Repository (lib/repositories/task_repository.dart
)
import 'package:hive/hive.dart';
import '../models/task.dart';
class TaskRepository {
late Box<Task> _box;
Future<void> initialize() async {
if (!Hive.isBoxOpen('tasksBox')) {
_box = await Hive.openBox<Task>('tasksBox');
} else {
_box = Hive.box<Task>('tasksBox');
}
}
Future<List<Task>> getAllTasks() async {
return _box.values.toList();
}
Future<void> addTask(Task task) async {
await _box.put(task.id, task);
}
Future<void> updateTask(Task task) async {
await _box.put(task.id, task);
}
Future<void> deleteTask(String id) async {
await _box.delete(id);
}
Future<void> toggleTaskCompletion(String id) async {
final task = _box.get(id);
if (task != null) {
task.isCompleted = !task.isCompleted;
await task.save();
}
}
}
3. Main Application (lib/main.dart
)
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'models/task.dart';
import 'screens/home_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive
await Hive.initFlutter();
// Register adapters
Hive.registerAdapter(TaskAdapter());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hive Task Manager',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeScreen(),
);
}
}
4. Home Screen (lib/screens/home_screen.dart
)
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../models/task.dart';
import '../repositories/task_repository.dart';
import 'add_task_screen.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
late TaskRepository _taskRepository;
late Future<void> _initializationFuture;
@override
void initState() {
super.initState();
_taskRepository = TaskRepository();
_initializationFuture = _taskRepository.initialize();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Task Manager'),
),
body: FutureBuilder(
future: _initializationFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ValueListenableBuilder(
valueListenable: Hive.box<Task>('tasksBox').listenable(),
builder: (context, Box<Task> box, _) {
final tasks = box.values.toList();
if (tasks.isEmpty) {
return Center(
child: Text('No tasks yet. Add one!'),
);
}
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return Dismissible(
key: Key(task.id),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Icon(
Icons.delete,
color: Colors.white,
),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
_taskRepository.deleteTask(task.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Task deleted')),
);
},
child: ListTile(
title: Text(
task.title,
style: TextStyle(
decoration: task.isCompleted
? TextDecoration.lineThrough
: null,
),
),
subtitle: Text(task.description),
trailing: Checkbox(
value: task.isCompleted,
onChanged: (value) {
_taskRepository.toggleTaskCompletion(task.id);
},
),
),
);
},
);
},
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTaskScreen()),
);
},
),
);
}
}
5. Add Task Screen (lib/screens/add_task_screen.dart
)
import 'package:flutter/material.dart';
import '../models/task.dart';
import '../repositories/task_repository.dart';
class AddTaskScreen extends StatefulWidget {
@override
_AddTaskScreenState createState() => _AddTaskScreenState();
}
class _AddTaskScreenState extends State<AddTaskScreen> {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
late TaskRepository _taskRepository;
@override
void initState() {
super.initState();
_taskRepository = TaskRepository();
_taskRepository.initialize();
}
@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add Task'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _titleController,
decoration: InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a title';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(),
),
maxLines: 3,
),
SizedBox(height: 24),
ElevatedButton(
child: Text('Save Task'),
onPressed: () {
if (_formKey.currentState!.validate()) {
final newTask = Task(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text,
description: _descriptionController.text,
createdAt: DateTime.now(),
);
_taskRepository.addTask(newTask);
Navigator.pop(context);
}
},
),
],
),
),
),
);
}
}
Conclusion
Hive provides a fast, easy-to-use database solution for Flutter applications. Its type-safe API and seamless integration with the Flutter ecosystem make it an excellent choice for local data persistence. By following the patterns and practices outlined in this guide, you can effectively implement Hive in your Flutter projects and create applications with robust data handling capabilities.
For more advanced uses, explore the official Hive documentation at https://docs.hivedb.dev/.