Hive Prem Datastorer
Hive Prem Datastorer
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
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.
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.
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
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';
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.
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
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';
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';
@HiveField(1)
HiveList friends;
Person(this.name);
String toString() => name; // For print()
}
@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.
@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');