0% found this document useful (0 votes)
3 views22 pages

Hive Prem Datastorer

This document provides a comprehensive guide on using Hive, a lightweight database for Flutter applications. It covers installation, initialization, data storage in boxes, reading and writing data, and the use of TypeAdapters for custom objects. Additionally, it explains how to manage data efficiently with features like ValueListenableBuilder and auto-increment keys, along with best practices for updating classes and handling enums.

Uploaded by

DHMC LAHORE
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views22 pages

Hive Prem Datastorer

This document provides a comprehensive guide on using Hive, a lightweight database for Flutter applications. It covers installation, initialization, data storage in boxes, reading and writing data, and the use of TypeAdapters for custom objects. Additionally, it explains how to manage data efficiently with features like ValueListenableBuilder and auto-increment keys, along with best practices for updating classes and handling enums.

Uploaded by

DHMC LAHORE
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

Quick Start

Before you start: Consider using Isar a Flutter database by the author of Hive that is superior in every way!
Add to project
Add the following to your pubspec.yaml:
dependencies:
hive: ^[version]
hive_flutter: ^[version]
dev_dependencies:
hive_generator: ^[version]
build_runner: ^[version]

Initialize
Initializes Hive with a valid directory in your app files. You can also provide a subdirectory:
await Hive.initFlutter();
Use Hive.init() for non-Flutter apps.
Open a Box
All of your data is stored in boxes.
var box = await Hive.openBox('testBox');
You may call box('testBox') to get the singleton instance of an already opened box.
Read & Write
Hive supports all primitive types, List, Map, DateTime, BigInt and Uint8List. Any object <custom object> can be
stored using TypeAdapters.
import 'package:hive/hive.dart';
void main() async {
// Hive.init('somePath') -> not needed in browser

var box = await Hive.openBox('testBox'); // create a box on provided path


box.put('name', 'David');
print('Name: ${box.get('name')}');
}

What are boxes?


All data stored in Hive is organized in boxes. A box can be compared to a table in SQL, but it does not have
a structure and can contain anything.
For a small app, a single box might be enough. For more advanced problems, boxes are a great way to
organize your data. Boxes can also be encrypted to store sensitive data.
Open Box
Before you can use a box, you have to open it. For regular boxes, this loads all of its data from the local
storage into memory for immediate access.
var box = await Hive.openBox<E>('testBox');
Once you obtained a box instance, you can read, write, and delete entries.
Parameter Description

The name of the box specifies the storage location and is used to check if a box
name
already exists. It is case-insensitive.

The key has to be a byte array with length 32 and is used to encrypt and decrypt all
encryptionKey
values in the box.

By default, keys are sorted lexicographically. This parameters allows you to provide
keyComparator
a custom sorting order.

compactionStrategy Specify your own rules for automatic compaction.


Parameter Description

If your app is killed while a write operations is running, the last entry might be
crashRecovery corrupted. This entry is deleted automatically when the app starts again. If you don't
want this behavior, you can disable it.

By default, boxes are stored in the directory given to Hive.init(). With this parameter
path
you can specify the location where the box should be stored.

Instead of using a file as backend, you can provide the box in binary form and open
bytes
an in-memory box.

E The optional type parameter specifies the type of the values in the box.

Get open box


Hive stores a reference to all open boxes. If you want to get an already opened box, you can use
var box = Hive.box('myBox');
This method is especially useful for Flutter apps because you don't need to pass the box between widgets.
Close box
If you don't need a box again, you should close it. All cached keys and values of the box will be dropped from
memory and the box file is closed after all active read and write operations finished.
It is perfectly fine to leave a box open for the runtime of the app. If you need a box again in the future, just leave
it open.
var box = await Hive.openBox('myBox');
await box.put('hello', 'world');
await box.close(); // close single box
Hive.close() ; // close all open boxes
Before your application exits, you should call Hive.close() to close all open boxes. Don't worry if the
app is killed before you close Hive, it doesn't matter.
Type parameter: Box<E>
When you open a box, you can specify that it may only contain values of a specific type. For example, a
user box could be opened like this:
var box = await Hive.openBox<User>('users');
box.add(User());
box.add(5); // Compile time error
This box can also contain subtypes of User.
It is important that you provide the same type parameter to Hive.box(). You cannot open the same box
multiple times with different type parameters.
await Hive.openBox<User>('users'); // Note the type parameter
Hive.box<User>('users'); // OK, as type parameter matched with above
Hive.box('users'); // ERROR // without type parameter, dynamic is considered
Hive.box<Dog>('users'); // ERROR
Generic type parameters like Box<List> are unsupported due to Dart limitations.

Reading and Writing


Read
Reading from a box is very straightforward:
var box = Hive.box('myBox');
String name = box.get('name');
DateTime birthday = box.get('birthday');

If the key does not exist, null is returned. Optionally you can specify a defaultValue that is returned in case
the key does not exist.
double height = box.get ( 'randomKey', defaultValue: 17.5 );
Lists returned by get() are always of type List<dynamic> (Maps of type Map<dynamic, dynamic>).
Use list.cast<SomeType>() to cast them to a specific type.
Every object only exists once
You always get the same instance of an object from a specific key. It does not matter for primitive values since
primitives are immutable, but it is essential for all other objects.
Here is an example:
The initialList and myList are the same instance and share the same data.
In the sample above, only the cached object has been changed and not the underlying data. To persist the
change, box.put('myList', myList) needs to be called.
Write
Writing to a box is almost like writing to a map. All keys have to be ASCII Strings with a max length of
255 chars or unsigned 32 bit integers.
var box = Hive.box('myBox');
box.put('name', 'Paul');
box.put('friends', ['Dave', 'Simon', 'Lisa']);
box.putAll({'key1': 'value1', 42: 'life'});
You may wonder why writing works without async code. This is one of the main strengths of Hive.
The changes are written to the disk as soon as possible in the background but all listeners are notified
immediately. If the async operation fails (which it should not), all listeners are notified again with the old values.
If you want to make sure that a write operation is successful, just await its Future.
This works differently for lazy boxes: As long as the Future returned by put() did not finish, get() returns
the old values (or null if it doesn't exist).
The following code shows the difference:
var box = await Hive.openBox('box');

box.put('key', 'value');
print(box.get('key')); // value

var lazyBox = await Hive.openLazyBox('lazyBox');

var future = lazyBox.put('key', 'value');


print(lazyBox.get('key')); // null

await future;
print(lazyBox.get('key')); // 'value'

Delete
If you want to change an existing value, you can either override it using for example put() or delete it:
If the key does not exist, no disk access is needed and the returned Future finishes immediately.
Write null vs delete: Writing null is NOT the same as deleting a value.
import 'package:hive/hive.dart';
void main() async {
var box = await Hive.openBox('writeNullBox');
box.put('key', 'value');
box.put('key', null);

print(box.containsKey('key')); // true
box.delete('key');
print(box.containsKey('key')); / / false
}

Hive in Flutter
Hive supports all platforms supported by Flutter.
Initialize Flutter Apps
Before you can open a box, Hive needs to know where it can store its data. Android and iOS have very strict
rules for allowed directories. You can use the path_provider package to get a valid directory.
The hive_flutter package provides the Hive.initFlutter() extension method which handles everything for you.
ValueListenable
If you want your widgets to refresh based on the data stored in Hive, you can use the ValueListenableBuilder.
The box.listenable() method provides a ValueListenable which can also be used with the provider package.
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {


await Hive.initFlutter(); // internally use “path provider” package to get the application document dir
await Hive.openBox('settings');
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo Settings',
home: Scaffold(
body: ValueListenableBuilder<Box>(
valueListenable: Hive.box('settings').listenable(),
builder: (context, box, widget) {
return Center(
child: Switch(
value: box.get('darkMode', defaultValue: false),
onChanged: (val) {
box.put('darkMode', val);
},
),
);
},
),
),
);
}
}
Each time the value associated with darkMode changes, the builder is called, and the Switch widget refreshed.
Listening for specific keys
It is good practice to refresh widgets only if necessary. If a widget only depends on specific keys in your box,
you can provide the keys parameter:
ValueListenableBuilder<Box>(
valueListenable: Hive.box('settings').listenable(keys: ['firstKey', 'secondKey']),
builder: (context, box, widget) {
// build widget
},
)

Async calls
Many of the Hive methods like put() or delete() are asynchronous. Handling async calls in Flutter is not fun
because you have to use FutureBuilder or StreamBuilder and handle exceptions.
The good news is that you don't have to await all the Futures returned by Hive. Changes are applied instantly,
even if the Future has not finished yet. If you want to make sure that a write operation is successful, just await
its Future.

Auto increment & indices


We already know that Hive supports unsigned integer keys. You can use auto-increment keys if you like. This is
very useful for storing and accessing multiple objects. You can use a Box like a list.
import 'package:hive/hive.dart';

void main() async {


var friends = await Hive.openBox('friends');
friends.clear();

friends.add('Lisa'); // index 0, key 0


friends.add('Dave'); // index 1, key 1
friends.put(123, 'Marco'); // index 2, key 123
friends.add('Paul'); // index 3, key 124

print(friends.getAt(0));
print(friends.get(0));

print(friends.getAt(2));
print(friends.get(123));
}
There are also getAt(), putAt() and deleteAt() methods to access or change values by their index.
By default, String keys are sorted lexicographically and they have also indices.
Even if you only use auto increment keys, you should not rely on keys and indices being the same.

TypeAdapters
Hive supports all primitive types, List, Map, DateTime and Uint8List. If you want to store other objects, you
have to register a TypeAdapter which converts the object from and to binary form.
You can either write a TypeAdapter yourself or generate it. Most of the time the generated adapter will perform
really good. Sometimes there are small things you can improve with a manually written adapter.
Register Adapter
When you want Hive to use a TypeAdapter, you have to register it. Two things are needed for that: An
instance of the adapter and a typeId. Every type has a unique typeId which is used to find the correct adapter
when a value is brought back from disk. All typeId between 0 and 223 are allowed.
Hive.registerAdapter(MyObjectAdapter());

Make sure to use typeId consistently. Your changes have to be compatible to previous versions of the box.
It's recommended to register all TypeAdapter before opening any boxes.
import 'package:hive/hive.dart';
class User {
String name;
User(this.name);
@override
String toString() => name; // Just for print()
}
void main() async {
Hive.registerAdapter(UserAdapter()); // Register Adapter

var box = await Hive.openBox<User>('userBox');


box.put('david', User('David'));
box.put('sandy', User('Sandy'));
print(box.values);
}

class UserAdapter extends TypeAdapter<User> { // Creating adapter automatically


@override
final typeId = 0;
@override
User read(BinaryReader reader) {
return User(reader.read());
}
@override
void write(BinaryWriter writer, User obj) {
writer.write(obj.name);
}
}

Generate adapter
The hive_generator package can automatically generate TypeAdapters for almost any class.
1. To generate a TypeAdapter for a class, annotate it with @HiveType and provide a typeId (between 0 and
223)
2. Annotate all fields which should be stored with @HiveField
3. Run build task flutter packages pub run build_runner build
4. Register the generated adapter
Example
Given a library person.dart with a Person class annotated with @HiveType with a unique typeId argument:
import 'package:hive/hive.dart';
part 'person.g.dart';

@HiveType(typeId: 1)
class Person extends HiveObject{
@HiveField(0)
String name;
@HiveField(1)
int age;
@HiveField(2)
List<Person> friends;
}
As you can see, each field annotated with @HiveField has a unique number (unique per class). These field
numbers are used to identify the fields in the Hive binary format, and should not be changed once your class is
in use.
Field numbers can be in the range 0-255.
The above code generates an adapter class called PersonAdapter. You can change that name with the
optional adapterName parameter of @HiveType.
Updating a class
If an existing class needs to be changed – for example, you'd like the class to have a new field – but you'd still
like to read objects written with the old adapter, don't worry! It is straightforward to update generated adapters
without breaking any of your existing code. Just remember the following rules:
• Don't change the field numbers for any existing fields.
• If you add new fields, any objects written by the "old" adapter can still be read by the new
adapter. These fields are just ignored. Similarly, objects written by your new code can be read by
your old code: the new field is ignored when parsing.
• Fields can be renamed and even changed from public to private or vice versa as long as the field
number stays the same.
• Fields can be removed, as long as the field number is not used again in your updated class.
• Changing the type of a field is not supported. You should create a new one instead.
• You have to provide defaultValue for new non-nullable fields after enabling null safety.

Enums
Generating an adapter for enums works almost as it does for classes:
@HiveType(typeId: 2)
enum HairColor {
@HiveField(0)
brown,
@HiveField(1)
blond,
@HiveField(2)
black,
}
For updating the enum, the same rules apply as above.
Default value
You can provide default values to properties and fields by providing defaultValue argument
to @HiveField annotation.

@HiveType(typeId: 2)
class Customer {
@HiveField(1, defaultValue: 0.0)
double balance;
}
Default values for custom types were introduced after hive: 2.0.4 and hive_generator: 1.1.0.
You can also provide default value for enum types by setting defaultValue to true. If you have not set default
value for enum types, the first value will be used as default value.

@HiveType(typeId: 2)
enum HairColor {
@HiveField(0)
brown,
@HiveField(1)
blond,
@HiveField(2, defaultValue: true)
black,
}

HiveObject
When you store custom objects in Hive you can extend HiveObject to manage your objects easily.
HiveObject provides the key of your object and useful helper methods like save() or delete().
Here is an example how to use HiveObject:
import 'package:hive/hive.dart';

void main() async {


Hive.registerAdapter(PersonAdapter());
var persons = await Hive.openBox('persons');

var person = Person()


..name = 'Lisa';
persons.add(person); // Store this object for the first time
print('Number of persons: ${persons.length}');
print("Lisa's first key: ${person.key}");

person.name = 'Lucas';
person.save(); // Update object
person.delete(); // Remove object from Hive

persons.put('someKey', person);
print("Lisa's second key: ${person.key}");
}
@HiveType()
class Person extends HiveObject {
@HiveField(0)
String name;
}
class PersonAdapter extends TypeAdapter<Person> {
@override
final typeId = 0;
@override
Person read(BinaryReader reader) {
return Person()..name = reader.read();
}
@override
void write(BinaryWriter writer, Person obj) {
writer.write(obj.name);
}
}
You also need to extend HiveObject if you want to use queries.

Relationships
Sometimes your models are connected with each other. The following person class has a list of other persons
called "friends". There could also be a list of other objects like "pets".
class Person extends HiveObject {
String name;
int age;
List<Person> friends;
Person(this.name, this.age);
}
You could just use a regular list to store the persons, but updating a person would be rather complicated
because the person objects would be stored redundantly.
HiveLists
HiveLists provide an easy way to solve the problem above. They allow you to store a "link" to the actual object.
import 'package:hive/hive.dart';

void main() async {


Hive.registerAdapter(PersonAdapter());
var persons = await Hive.openBox<Person>('personsWithLists');
persons.clear();

var mario = Person('Mario');


var luna = Person('Luna');
var alex = Person('Alex');
persons.addAll([mario, luna, alex]);

mario.friends = HiveList(persons); // Create a HiveList


mario.friends.addAll([luna, alex]); // Update Mario's friends
mario.save(); // make persistent the change,
print(mario.friends);

luna.delete(); // Remove Luna from Hive


print(mario.friends); // HiveList updates automatically
}
@HiveType()
class Person extends HiveObject {
@HiveField(0)
String name;

@HiveField(1)
HiveList friends;

Person(this.name);
String toString() => name; // For print()
}

class PersonAdapter extends TypeAdapter<Person> {


@override
final typeId = 0;

@override
Person read(BinaryReader reader) {
return Person(reader.read())..friends = reader.read();
}

@override
void write(BinaryWriter writer, Person obj) {
writer.write(obj.name);
writer.write(obj.friends);
}
}
First, we store the three persons, Mario, Luna, and Alex in the persons box. HiveLists can only be used with
objects which are currently in a box.
Next, we create a HiveList which contains Mario's friends. The HiveList constructor needs the HiveObject, which
will contain the list. The list must not be moved to another HiveObject. The second parameter is the box, which
contains the items of the list.
When you delete an object from a box, it is also deleted from all HiveLists. If you delete an object from a HiveList,
it still remains in the box.

Create adapter manually


Sometimes it might be necessary to create a custom TypeAdapter. You can do that by extending
the TypeAdapter class. Make sure to specify the generic argument.
Test your custom TypeAdapter thoroughly. If it does not work correctly, it may corrupt your box.
It is very easy to implement a TypeAdapter. Keep in mind that TypeAdapters have to be immutable! Here is
the DateTimeAdapter used by Hive internally:
class DateTimeAdapter extends TypeAdapter<DateTime> {
@override
final typeId = 16;

@override
DateTime read(BinaryReader reader) {
final micros = reader.readInt();
return DateTime.fromMicrosecondsSinceEpoch(micros);
}

@override
void write(BinaryWriter writer, DateTime obj) {
writer.writeInt(obj.microsecondsSinceEpoch);
}
}
As of Hive 1.3.0, all adapters require a typeId instance variable!
The typeId instance variable assigns the number to be registered to that adapter. It has to be unique between all
adapters. The read() method is called when your object has to be read from the disk. Use the BinaryReader to
read all the properties of your object. In the above sample, it is only an int containing microsecondsSinceEpoch.
The write() method is the same just for writing the object to the disk.
Make sure, you read properties in the same order you have written them before.

Lazy box
By default, the entire content of a box is stored in memory as soon as it is opened. This behavior is great for
small and medium-sized boxes because you can access their contents without async calls.
For larger boxes or very memory critical applications, it may be useful to load values lazily. When a lazy box is
opened, all of its keys are read and stored in memory. Once you access a value, Hive knows its exact position
on the disk and gets the value.
var lazyBox = await Hive.openLazyBox('myLazyBox');

var value = await lazyBox.get('lazyVal');


To get an already opened lazy box call:
var lazyBox = Hive.lazyBox('myLazyBox');
Encrypted box
Sometimes it is necessary to store data securely on the disk. Hive supports AES-256 encryption out of the box
(literally).
The only thing you need is a 256-bit (32 bytes) encryption key. Hive provides a helper function to generate a
secure encryption key using the Fortuna random number generator.
Just pass the key when you open a box:
import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

void main() async {


const secureStorage = FlutterSecureStorage();
final encryptionKey = await secureStorage.read(key: 'key');
if (encryptionKey == null) { // if key not exists return null
final key = Hive.generateSecureKey();
await secureStorage.write(
key: 'key',
value: base64UrlEncode(key),
);
}
final key = await secureStorage.read(key: 'key');
final encryptionKey = base64Url.decode(key!);
final encryptedBox= await Hive.openBox('vaultBox', encryptionCipher: HiveAesCipher(encryptionKey));
encryptedBox.put('secret', 'Hive is cool');
print(encryptedBox.get('secret'));
}
The example above stores the encryption key using the flutter_secure_storage package, but you can use any
package/method you prefer for securely storing the encryption key when your application is closed.
Important:
• Only values are encrypted while keys are stored in plaintext.
• There is no check if the encryption key is correct. If it isn't, there may be unexpected behavior.
Compaction
Hive is an append-only data store. When you change or delete a value, the change is written to the end of the
box file. Sooner or later, the box file uses more disk space than it should. Hive may automatically "compact"
your box at any time to close the "holes" in the file.
It may benefit the start time of your app if you induce compaction manually before you close a box.
var box = Hive.box('myBox');
await box.compact();
await box.close();
You can specify your own rules for automatic compaction. Just pass the compactionStrategy parameter when you
open a box:
var box = await Hive.openBox('myBox', compactionStrategy: (entries, deletedEntries) {
return deletedEntries > 50;
});
The compactionStrategy above will compact your box when 50 keys have been overridden or
deleted.
NEVER access a box from the compaction strategy.
Modelling data
Hive does not have a query language and only has limited support for sorting, which is not necessarily a
disadvantage, as you can see in the benchmark. Sorting and filtering are much faster if you do it yourself in
Dart.
Since key-value stores are really simple, data can be modeled in a less complicated manner than, for example,
in SQLite. There is no schema, and you can store objects of different types in the same box. But there are a
few things you should keep in mind.
Key order
All keys are sorted in lexicographic order by default. You can use that to get "free" ordering. For example, if you
want to store users, you could use the last names and a unique number as a key to sort them by their last
name.
You can also provide a custom key sorting function. For example, you could sort users in reverse lexicographic
order.
Lists vs Auto Increment
If you want to store a list of items, you have two options. Either store the list directly as a value
(using put('myKey', [1, 2, 3])) or you can store each item individually (using add(1), add(2) etc.).
If you want to store a big list which is updated frequently (for example, a list of messages), you should store the
items individually. Otherwise, the whole list has to be written each time you change an item.
Filtering items
You can easily filter items in your box. For example:
var filteredUsers = userBox.values.where((user) => user.name.startsWith('s'));
It might be a good idea to cache the result to improve performance.

You might also like