Local Persistant Storage in Flutter : Getting Started with HiveDB
Store data with local persistance in flutter apps with the help of hive database.
Overview
I have used HiveDB for local persistant storage in many of my apps and it is one of my favorite storage soutions when it comes to ease of use. I recently noticed that the docs for HiveDB are down and are not accessible. The pacakge is not maintained anymore and was last updated 2 years ago(as of Oct 2024), yet has a popularity score of 100% and is one of the most used databases for local persistance storage.
Since the docs are down I thought of writing a Getting Started guide for HiveDB.
We will discuss about the following in this article
- What is Hive Database?
- Getting Started with Hive
- Basic Hive Concepts
- Generating TypeAdapters
- Creating a simple Todo List app
- Conclusion
What is Hive Database?
Hive is a lightweight, efficient NoSQL database specifically designed for local persistent storage for Flutter applications. It is a high performant database with a key-value storage model. This makes it appropriate for use in scenerios were quick data retrival is crucial. Hive allows creation of someting called as TypeAdapters which allow storing any type object, in addition to the primitive data types like List, Map, DateTime, BigInt and Uint8List that hive supports out of the box.
Getting Started with Hive
In order to use hive we need to add the following dependencies to the pubspec.yaml file
dependencies:
analyzer: ^4.7.0
flutter:
sdk: flutter
#hiveDB related packages
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
#package to generate dart code
build_runner: ^2.4.8
#hive generator for generating type adapters
hive_generator: ^1.1.0Let’s first go through the dependencies
- hive: The package required to use hive database
- hive_flutter: This package makes it easier to use hive in flutter apps.
- build_runner: This package is used to generate dart code.
- hive_generator: This package is used to generate
TypeAdapterswhich can be stored in hive boxes.
Next, we need to initialize hive in the main function
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
//Initialize Hive
await Hive.initFlutter();
runApp(const MainApp());
}This will initialize hive and will store the hive box in a valid directory which changes based on the operating system. By default, Hive is initialized in the application documents directory. We can optionally specify the directory in which Hive should be initialized.
await Hive.initFlutter('path_to_directory');This is all the setup needed in order to start using hive. Just add all the required dependencies and use await Hive.initFlutter(); to initialize hive, that’s it.
Basic Hive Concepts
In order to work with hive we need to know about some basic hive concepts.
Hive Boxes
Hive stores data inside boxes in the form of key-value pair. We first need to open a hive box before storing any data. This can be done by using the openBox command
var todosBox = await Hive.openBox('todos');This will create a hive box where we can store our data. If we navigate to the directory where we have initilized Hive we will notice a new file by the name of todos.hive. For example, if a windows application is launched, then the box files by the extension of .hive can be found in Documents dir. This is the file which will store all the key-value pairs.
Operations on Hive Boxes
We can easily perform CRUD(Create, Read, Update, Delete) operations on Hive Boxes.
//Initialize Hive
await Hive.initFlutter();
// Open the box after initializing Hive
var todosBox = await Hive.openBox('todos');
//CREATE
// Store title using put method by specifying the key and value to store
todosBox.put('title', 'Football Match');
debugPrint('Length: ${todosBox.length}');
//READ
// Retrive the title using get method by specifying the key
var title = todosBox.get('title');
debugPrint('Title : $title');
//UPDATE
// Update the title using the put method by specifying the key
todosBox.put('title', 'Grocery Shopping');
// Retrive the updated title using get by specifying the key
title = todosBox.get('title');
debugPrint('Updated title: $title');
//DELETE
// delete data using delete by specifying the key
todosBox.delete('title');
debugPrint('Length: ${todosBox.length}');We have performed CRUD operations on todosBox using put(), get() and delete() methods. These methods all have a required parameter ‘key’ which is used to store and access data.
Get Bhavesh Mankar’s stories in your inbox
Join Medium for free to get updates from this writer.
Hive also supports auto incrementing keys which allows storing data without specifying ‘key’, instead hive automatically generates incrementing indices and provides methods like add(), getAt(), putAt() and deleteAt() for CRUD operations.
We can strore any data in hive as long as we are storing primitive data types, but that’s no fun. What if we want to store some custom data? This is were TypeAdapters come into paly.
Generating TypeAdapters
Hive allows storing custom objects with the help of TypeAdapters, but creating TypeAdapters requires boilerplate code. Fortunately, we can make use of build_runner package to generate TypeAdapters with the help of hive_generator.
In order to generate TypeAdapers we need to do some setup. Let’s look at todo_model.dart.
//FILE: todo_model.dart
import 'package:hive/hive.dart';
//specify name of the part file which will be generated by hive_generator
part 'todo_model.g.dart';
//Specify the HiveType with a type id between 0 and 233
//NOTE: No two TypeAdapters can have the same typeId
@HiveType(typeId: 0)
class Todo {
//Annotate class members with HiveField with each
//HiveField having incrementing index
@HiveField(0)
String title;
@HiveField(1)
String description;
//Default value can be specified if needed
@HiveField(3, defaultValue: false)
bool isDone;
Todo({
required this.title,
required this.description,
this.isDone = false,
});
}Now, let’s breakdown the structure of todo_model.dart
First: We have specified the name that the generated file will have as todo_model.g.dart here ‘g’ specifies that it is a generated file.
Second: We have annotated the class with HiveType which takes an index. Every single class annotated with HiveType should have a unique index between 0 to 233
Third: We have annotated the class members with HiveField each having a incrementing index. Also we have specified defaultValue for isDone so if no value is provided isDone will default to false. HiveField can have a range of 0 to 255
That’s is thats all the code that we need for generating a TypeAdapter . Now, in order to generate a TypeAdapter type the below command in terminal window.
dart run build_runner buildAfter executing the command a file named todo_model.g.dart will be generated in the same folder as todo_model.dart . This file will contain the code generated by hive_generator package.
The file will have a class named TodoAdapter extending TypeAdapter<Todo> and all the logic that is required to convert data from and to binary. We do not need to know any more information about this file.
Next Up, in order to use the TypeAdapter, we first need to register it.
void main() async {
//Initialize Hive
await Hive.initFlutter();
//Register a TypeAdapter
Hive.registerAdapter(TodoAdapter());
}Now, that we have learned about all basic concepts of Hive and can kick things off by looking at a Todo List app.
Creating a simple Todo List app
The Todo List app will have a HomePage widget with a ListView and a FloatingActionButton.
- The
initStatemethod creates a box called astodosBoxand callsloadTodos.TheloadTodosfunction fetches the list of todos stored in thetodosBox. After fetching the Todo items from the box it parses it as list and stores intodosList - If the
todosListis not empty then List View will return ListTiles. Each ListTile will have its
titleas title of the Todo,subtitleas the description of Todo and a- Checkbox as the
tralingwidget which will track isDone property of Todo - IconButton as
leadingwidget for deleting the Todo
3. On pressing the FloatingActionButton a SimpleDialog will be displayed with two TextFields for title and description of the Todo. Pressing ADD will add the Todo to the todosBox.
4. CRUD operations are handelled by these function functions
- addTodo(): The
addTodofunction takes aTodoobject as a parameter and adds it totodosBox. If the addition is successful, it updates thetodosListby appending the newly created todo. - deleteTodo(): The
deleteTodofunction provides users with the ability to remove completed or unnecessary todo items from their lists. It accepts an index and attempts to delete the corresponding todo from thetodosBox. If successful, it updates thetodosListto remove the deleted item. - changeCompletionStatus(): The
changeCompletionStatusaccepts an index and a boolean value, this function enables users to toggle the completion status of a specific todo item. When called, it retrieves the todo from the Hive database, updates itsisDoneproperty according to the provided value, and saves the modified todo back into thetodosBox. This function also updates thetodosListto reflect the change.
Here is the complete code:
//FILE: todo_model.dart
import 'package:hive/hive.dart';
//specify name of the part file which will be generated by hive_generator
part 'todo_model.g.dart';
//Specify the HiveType with a type id between 0 and 233
//NOTE: No two TypeAdapters can have the same typeId
@HiveType(typeId: 0)
class Todo {
//Annotate class members with HiveField with each
//HiveField having incrementing index
@HiveField(0)
String title;
@HiveField(1)
String description;
//Default value can be specified if needed
@HiveField(3, defaultValue: false)
bool isDone;
Todo({
required this.title,
required this.description,
this.isDone = false,
});
}
///FILE: main.dart
/// The main function initializes Hive and runs the Flutter application.
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:lib/todo_model.dart';
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(TodoAdapter());
await Hive.openBox<Todo>('todos');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late Box<Todo> todosBox;
List<Todo> todosList = [];
bool isLoading = true;
final TextEditingController _titleController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
@override
void initState() {
super.initState();
todosBox = Hive.box<Todo>('todos');
loadTodos();
}
/// Loads todos from the Hive box and stores it in todosList.
Future<void> loadTodos() async {
setState(() {
isLoading = true;
});
todosList = todosBox.values.toList();
setState(() {
isLoading = false;
});
}
/// Adds a new todo to the Hive box and updates the todosList.
Future<void> addTodo({
required Todo todo,
}) async {
try {
await todosBox.add(todo);
setState(() {
todosList.add(todo);
});
debugPrint('${todo.title} added successfully');
} catch (e) {
debugPrint('Error: $e');
}
}
/// Deletes a todo from the Hive box and updates the todosList.
Future<void> deleteTodo(int index) async {
try {
await todosBox.deleteAt(index);
setState(() {
todosList.removeAt(index);
});
debugPrint('Todo Removed Successfully');
} catch (e) {
debugPrint('Error: $e');
}
}
/// Changes the completion status of a todo and updates the todosList.
Future<void> changeCompletionStatus(int index, {required bool value}) async {
try {
Todo todo = todosBox.getAt(index)!;
todo.isDone = value;
await todosBox.putAt(index, todo);
setState(() {
todosList[index] = todo;
});
debugPrint('${todo.title} marked ${value ? 'complete' : 'incomplete'}');
} catch (e) {
debugPrint('Error: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todo List'),
),
body: isLoading
? const Center(
child: CircularProgressIndicator(),
)
: todosList.isEmpty
? const Center(
child: Text("No Todos Created"),
)
: ListView.builder(
shrinkWrap: true,
itemCount: todosList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
todosList[index].title,
style: TextStyle(
decoration: todosList[index].isDone
? TextDecoration.lineThrough
: null,
),
),
subtitle: Text(
todosList[index].description,
style: TextStyle(
decoration: todosList[index].isDone
? TextDecoration.lineThrough
: null,
),
),
leading: Checkbox(
value: todosList[index].isDone,
onChanged: (value) {
changeCompletionStatus(index,
value: !todosList[index].isDone);
},
),
trailing: IconButton(
onPressed: () async {
await deleteTodo(index);
},
icon: const Icon(
Icons.delete_forever,
color: Colors.redAccent,
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: const Text("Create a Todo"),
contentPadding:
const EdgeInsets.symmetric(vertical: 18, horizontal: 16),
children: [
Column(
children: [
TextField(
controller: _titleController,
decoration: const InputDecoration(
border: OutlineInputBorder(), label: Text("Title")),
),
const SizedBox(
height: 12,
),
TextField(
controller: _descriptionController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
label: Text("Description")),
)
],
),
const SizedBox(height: 22),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(widget);
},
child: const Text("CANCEL")),
ElevatedButton(
onPressed: () async {
if (_titleController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
duration: Duration(milliseconds: 200),
content: Text("Title cannot be empty")));
} else {
addTodo(
todo: Todo(
title: _titleController.text,
description: _descriptionController.text,
),
).then((_) {
_titleController.clear();
_descriptionController.clear();
});
}
Navigator.of(context).pop();
},
child: const Text("ADD"),
),
],
)
],
);
},
);
},
child: const Icon(Icons.add),
),
);
}
}This is how the output will look,
Conclusion
In this article, we explored Hive, a lightweight and fast NoSQL database for Flutter applications. We learned how to set up Hive, understood its basic concepts, and generated TypeAdapters for our data models. We also created a simple Todo List app to see how Hive works in action.
Hive is a great choice as it offers a simple and efficient way to manage local data. Even though Hive is no longer maintained it is still used by many developers. Its ease of use, performance, and flexibility make it a great choice.








