HIVE the right way !

HIVE the right way !

So in this blog we are gonna talk about using HiveDB in your Flutter applications.

You all might be familiar with SharedPreferences right ? , if not thats okay... SharedPreferences are the first choice of all App Developers when it comes to storing user data locally. SharedPreferences lets the developers store user's data locally (for easier access) in the form of key-value pairs. The data could include but not limited to User Theme Preferences , Non-critical DB Tokens , Session tokens , and so on.

But there are some limitations with them...

  • Firstly, it is asynchronous. ie, Accessing the SharedPreference instance is an async task in Flutter. Though the wait is very small, it does require the developer to use await and async to access the SharedPreference instance and eventually the items stored in it.
  • Secondly, it doesn't support NoSQL Structure. In order to store certain User Defined objects, we need to perform serialization to JSON to store and deserialize to retrieve back the object.

Keeping this in mind, Enter HiveDB !!

Hive is a lightweight and blazing fast key-value database written in pure Dart. Inspired by Bitcask.

Table of contents

  1. Setting up Hive in your app
  2. Hive Boxes
  3. Storing and Retrieving Primitive Datatypes
  4. TypeAdapters
  5. Storing Notes objects
  6. Application: Implementing Dark Mode in apps using Hive

Setting up Hive DB in your app

In your pubspec.yaml file add the following :

dependencies:
  hive: ^2.0.4
  hive_flutter: ^1.0.0
  path_provider: ^2.0.1

dev_dependencies:
  hive_generator: ^1.1.0
  build_runner: ^2.0.1

The versions of the package can vary, just refer the same from pub.dev

hive : The main package
hive_flutter : Extension for Hive. Makes it easier to use Hive in Flutter apps.
hive_generator : Extension for Hive. Automatically generates TypeAdapters to store any class.
build_runner : A build system for Dart code generation and modular compilation.
path_provider : Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.

Well, that's all with the package setups !! Let's move forward and see the setup required in our app code.

Write the following code before inside main() but before runApp(MyApp()); :

WidgetsFlutterBinding.ensureInitialized();
final directory = await path.getApplicationDocumentsDirectory();
Hive.init(directory.path);

The WidgetFlutterBinding is used to interact with the Flutter engine. Hive.init(directory.path); needs to call native code to initialize Hive, and since the plugin needs to use platform channels to call the native code, which is done asynchronously, therefore, you have to call ensureInitialized() to make sure that you have an instance of the WidgetsBinding.
The directory path can be anything that the developer decides, doesn't necessarily have to be the default App Documents Directory.

With this, we have initialized Hive in our Flutter app.

Note: All the Hive-related processes and initializations such as Opening HiveBoxes, Attaching adapters are written before runApp(MyApp());.

Hive Boxes

Anything that is stored in Hive is stored in boxes !! Boxes act as a container (not Flutter's Container) in which similar items can be stored. A Hive DB can have any number of boxes , and each box can have any number of items.
Boxes are an excellent way to organize similar data. It provides encryption of its own, so it can also be used to store sensitive information.

So before you can use a Box in Flutter, you have to name the box and open it beforehand. It can be done using the following code :

await Hive.openBox("preferences");

This code returns an Box. You can store it in a variable if you want to use it instantly or write it without assigning to any variable. Both ways , the box with the name "preferences" is opened or created if the box dosent exist.
But note that, u need to store the box in a variable in order to perform any functions on the box.
Now we can access the contents of the box anywhere synchronously (ie, without using await and async).

To close the box when not required, you can use the following code:

Hive.close() // Closes all the open Boxes

var box = await Hive.openBox("preferences");
box.close() // Closes the 'preferences' box

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.

Storing and Retrieving Primitive datatypes

An item can be stored into a box in 3 different ways :

var box = await Hive.openBox("preferences");

box.put("name","Aswin"); // "name" : key , "Aswin" : value
box.putAt(0, "Flutter"); // 0: index , "Flutter" : value

box.putAll({
    "name":"aswin",
    "age":"21"
  });
  • In the first method, we pass a key-value pair to be stored in the DB. Here, key is name and value associated with the key is Aswin.
  • In the second method, we pass the index to which the value has to be stored. Here, 0 is the index and "Flutter" is the value stored at that index.
  • In the third method, we pass a map variable consisting of keys and associated values to be stored in the box individually. It is like adding all the key-value pairs together instead than using a sequence of .put() statements.

Similarily, their respective values can be retrieved as :

box.get("name");
box.getAt(0);
  • .get(key) returns the value associated with the key passed as its argument.
  • .get(0) returns the item stored at index 0.

TypeAdapters

In the last section, we talked about how we can store and retrieve primitive data types. Primitive data types include Int, Float, Double, List, Map, DateTime, and Uint8List.
What if, we wish to store User Defined Objects such as a Model class. How can we do it ??

Enter TypeAdapters !!

If you want to store other objects, you have to register a TypeAdapter that converts the object from and to binary form as required.

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.

Consider the following Model class which we are later gonna use to develop our Notes App :

class Notes {
  String title;
  String description;

  Notes({this.title,this.description});
}

Eventhough this is the right way to define a Model class for a Note Item, but we cant store an instance of this class in the Hive Box. So we need to tweek this a bit so that it can be converted to a TypeAdapter Class.

For that we modify the above model class as follows :

import 'package:hive/hive.dart';
part 'noteModel.g.dart';

@HiveType(typeId: 0)
class Notes {

  @HiveField(0)
  String title;

  @HiveField(1)
  String description;

  Notes({this.title,this.description});
}

We assign a unique HiveType ID to the class , and unique HiveField ID to its individual attributes.

Note : No two classes can have the same HiveType ID and no two attributes belonging to the same class can have same HiveField ID , but attributes belonging to different classes can have the same HiveField ID.

The line part 'noteModel.g.dart'; is gonna show you an error. That's because it points to the TypeAdapter class which hasn't been created yet but which will be generated soon.

Now go to your terminal and enter the following code :

$ flutter packages pub run build_runner build

Wait for some time, after the command executes successfully you can see that the TypeAdapter class has been generated with the name mentioned in part.

Note : If you get scared easily on seeing complex codes, i suggest not to open the generated file :P

Now we have one last thing to do before we can store a Custom Object. We need to register the TypeAdapter. And the best place to do this is before runApp(MyApp()); and also before you open any boxes.

Hive.registerAdapter<Notes>(NotesAdapter());

Now we are all set to use Hive Box to store and retrieve and even delete the Notes Model class objects.

Storing Notes Objects

Now comes the best part !! Well, I know you already know how to do this part, right? But still here it goes...

box.putAt(0, Notes(title: "First Note",description: "I learnt how to use Hive"));

If you get any error such as :

[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: HiveError: Cannot read, unknown typeId: 32. Did you forget to register an adapter?

Just make sure you have generated the TypeAdpater properly and have registered it properly in the main().

Yaaaayyy !! You have made it till here !! A Pat in the back for you.... :claps: :claps:

NOW LETS GETS OUR HANDS DIRTY !! SHALL WE ?

Implementing Dark Mode in apps using Hive

Lets begin by opening a HiveBox named 'darkMode' :

 await Hive.openBox("darkMode");

Then in the main screen , lets create a switch that can be used to toggle the theme (dark/light) :

Switch(
   onChanged: (value) {
       Hive.box('darkMode').put("dark", value);
    },
    value: Hive.box('darkMode').get("dark",defaultValue: false),
 )

The value of the Switch is retrieved from the box which we had opened earlier. Initially if the box dosent have any value, a default value is used. Here, false.
Based on the state of the switch (ie, true/false),it is stored in the box.

But this is not enough, we need to attach a listener to the Theme object to change as and when the value in the box changes. For that we use the following code :

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WatchBoxBuilder(
      box: Hive.box('darkMode'),
      builder:(context,box) {
        bool dark = box.get("dark");
        return MaterialApp(
          title: 'Flutter Demo',
          themeMode: dark? ThemeMode.dark : ThemeMode.light,
          darkTheme: ThemeData.dark(),
          home: MyHomePage(title: 'Notes'),
        );
      }
    );
  }
}

We use WatchBoxBuilder to listen to a particular Box and the builder passes the box value to its body which is used to decide the theme of the app. As soon as the value in the box is changed, the changes are notified here and appropriate changes occur.


That's all folks for this Blog !! Hope you all liked it. If you liked it please do give a Thumbs Up , and if you feel there are some parts i need to work on or if you spotted any errors, please do mention in the comments or reach out to me.

This is my very first blog on HashNode. Stay connected with me for more such blogs... . Feel free to connect with me on :