Interesting Concept of Hive Database for Flutter

Hive is yet another powerful NoSQL Database designed purely in Dart Language. Additionally, it supports a variety of features compared to other tools. However, this blog post will cover all the related aspects of this database step by step, so let’s explore the Interesting Concept of Hive Database for Flutter.


Table of Contents

Advantages of using Hive

  • Cross Platform support
  • No Native dependencies are required
  • Strong support for encryption

Before moving on to our topic, there is a related blog post regarding using the SQFlite Database in Flutter, where I discussed the core usage of local databases and highlighted up to 4 key points.

Checkout the blog post (1st Heading)

Well, I need to add a fifth one, i.e.

Caching & Data Synchronization

Local databases can also be used as a cache for our remote source, which typically allows us to store the recently fetched data. This will improve performance, vis-à-vis fewer network requests in our application. Furthermore, this data can be synchronized with remote servers when Internet connectivity is available, ensuring data remains updated.

Let’s now move on to our topic.

Table of Contents

Project Scenario

Core Concepts of Hive

Hive Implementation

Conclusion

– Project Scenario

We shall design a CRUD for a Movie Application.

– Core Concepts of Hive

Note: It is mandatory to have your special attention as this section will cover some core aspects of Hive.

1. Hive Box

Hive uses the box.

A box is an unstructured approach to storing data. It doesn’t depend on a specific data type and can contain anything. However, it can be compared with an SQL table and vice versa.

Note: Before storing the data inside a box, we need to open it.

Here is the code snippet

await Hive.openBox('example_appbox');

In addition to this, specifying the type (model) of the box would result in more clarity.

The modified snippet would look like:

await Hive.openBox<ExampleModel>('example_appbox');

2. TypeAdapters

Hive supports primitive data types such as List, Map, DateTime, and Uint8List respectively. Other than these types, we need to register a TypeAdapter for them, which converts the object from and to binary format.

Two mandatory things are required when registering for a TypeAdapater:

  • typeId A unique ID used to identify the correct adapter, and typically ranges between 0 – 223 respectively
  • An instance of that adapter

Moreover, TypeAdapter can be written manually if you have a small App, comparatively, but the safest approach is to generate them via a dev_dependency plugin hive_generator.

Code snippet

Hive.registerAdapter(ExampleAppAdapter());

Note: It is recommended to register all TypeAdapters before opening any boxes.

– Hive Implementation

After creating a fresh Flutter project, head over to the pubspec.yaml and add the following plugins:

  • Under dependencies

hivefull-fledged Hive support plugin

hive_flutteran extension for using Hive in Flutter Apps

  • dev_dependencies

hive_generatorAn extension for generating TypeAdapters for storing any class

build_runnerA Dart code generation tool

Under the main.dart file where the root method is defined, add a new line:

await Hive.initFlutter();

Note: Be sure to add this import, import package:hive_flutter/hive_flutter.dart, otherwise, you won’t get this method.

In addition to this, open a new box with this command:

await Hive.openBox('movie_box');

– Movie model class

It will have the following:

  • id
  • title
  • thumbnail
  • genre
  • director

Create a new file movie.dart and add the following code snippet:

import 'package:hive/hive.dart';

part 'movie.g.dart';

@HiveType(typeId: 1)  //range b/w 0-223
class Movie {
  Movie({this.id,this.title,this.thumbnail, this.genre, this.director});
  @HiveField(0)
  int? id;

  @HiveField(1)
  String? title;

  @HiveField(2)
  String? thumbnail;

  @HiveField(3)
  String? genre;

  @HiveField(4)
  String? director;
}

In the above snippet, each field is annotated with @HiveField linked with a unique number used to identify the field itself.

Note: When you write this code, you shall see an error under,

part 'movie.g.dart';

Ignore this for some time because we will generate it using build_runner.

Note: The file name and the error statement must have the same title to generate a valid adapter.

Now, head over to the project’s terminal and jot down this command:

flutter packages pub run build_runner build

This will generate a new file of movie.dart and the error will go away.

build_runner_output

Note: Be sure to modify this code snippet, and specify the type.

await Hive.openBox<Movie>('movie_box');

Sample code of generated TypeAdapter

part of 'movie.dart';

// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************

class MovieAdapter extends TypeAdapter<Movie> {
  @override
  final int typeId = 1;

  @override
  Movie read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    final fields = <int, dynamic>{
      for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return Movie(
      id: fields[0] as int?,
      title: fields[1] as String?,
      thumbnail: fields[2] as String?,
      genre: fields[3] as String?,
      director: fields[4] as String?,
    );
  }

  /// Remainning ....

At this point, we need to register this TypeAdapter under the root method just after the openBox command.

Hive.registerAdapter(MovieAdapter());

This sums up the database part.

Let’s prepare our UI stuff, and apply the CRUD operations.

1- add_new_movie.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_hive_demo/const/hive_const.dart';
import 'package:flutter_hive_demo/model/movie.dart';
import 'package:hive/hive.dart';
import 'package:image_picker/image_picker.dart';

class AddNewMovie extends StatefulWidget {
  const AddNewMovie({Key? key}) : super(key: key);

  @override
  State<AddNewMovie> createState() => _AddNewMovieState();
}

class _AddNewMovieState extends State<AddNewMovie> {
  TextEditingController? titleC, genreC, directorC;

  String? moviePath;

  @override
  void initState() {
    // TODO: implement initState

    titleC = TextEditingController();
    genreC = TextEditingController();
    directorC = TextEditingController();

    moviePath = "";
    super.initState();
  }

  dynamic addMoviePicture() async {
    PickedFile? pf = await ImagePicker().getImage(source: ImageSource.gallery);

    if (pf!.path.isNotEmpty) {
      setState(() {
        moviePath = pf.path;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        padding: const EdgeInsets.all(8),
        children: [
          InkWell(
            onTap: () => addMoviePicture(),
            child: moviePath!.isNotEmpty
                ? Container(
                    height: 150,
                    margin: EdgeInsets.only(bottom: 20),
                    width: MediaQuery.of(context).size.width,
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(12),
                      child: Image.file(File(moviePath!), fit: BoxFit.cover),
                    ),
                  )
                : Container(
                    height: 150,
                    margin: EdgeInsets.only(bottom: 20),
                    width: MediaQuery.of(context).size.width,
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: const Align(
                      alignment: Alignment.center,
                      child: Icon(Icons.camera, size: 70, color: Colors.white),
                    ),
                  ),
          ),
          buildField(titleC!, 'Movie Title'),
          buildField(directorC!, 'Movie Director'),
          buildField(genreC!, 'Movie Genre'),
          Container(
            height: 50,
            margin: const EdgeInsets.only(top: 45),
            child: ElevatedButton(
              child: const Text("Add Movie"),
              onPressed: () async {
                Box movieBox = Hive.box<Movie>(HiveConstants.movieBox);

                await movieBox.put(
                  DateTime.now().toString(),
                  Movie(
                    title: titleC!.text,
                    thumbnail: moviePath,
                    genre: genreC!.text,
                    director: directorC!.text,
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Widget buildField(TextEditingController controller, String hint) => Padding(
        padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 8.0),
        child: TextFormField(
          controller: controller,
          decoration: InputDecoration(
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            hintText: hint,
          ),
        ),
      );
}

Since Hive writes the data in the form of a key-value pair, the following steps need to be kept in mind:

Be sure to validate the registered box to write the data.

The key is a dynamic type.

To keep it unique, I have used DateTime.now().toString().

2- movie_listitng.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_hive_demo/const/hive_const.dart';
import 'package:flutter_hive_demo/model/movie.dart';
import 'package:flutter_hive_demo/presentation/add_new_movie.dart';
import 'package:flutter_hive_demo/presentation/widgets/movie_display_custom.dart';
import 'package:hive_flutter/hive_flutter.dart';

class MovieListing extends StatelessWidget {
  const MovieListing({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Movie Listing"),
      ),
      body: ValueListenableBuilder<Box>(
        valueListenable: Hive.box<Movie>(HiveConstants.movieBox).listenable(),
        builder: (context, box, child) {

          return box.values.isEmpty ? const Center(
            child: Text("No Movie Listing!"),
          ) : ListView.separated(
            itemCount: box.values.length,
            itemBuilder: (context, i) {

              Movie item = box.getAt(i);
              return MovieDisplayCustom(movie: item);
            },
            separatorBuilder: (context, i) => const Divider(),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Text("Add"),
        onPressed: () => Navigator.of(context)
            .push(MaterialPageRoute(builder: (context) => const AddNewMovie())),
      ),
    );
  }
}

– ValueListenableBuilder

Note: The best thing about Hive is that it comes up with a listenable method under the box. That is the reason I’ve come up with ValueListenableBuilder<T> to refresh the data being stored recently.

Now, it’s time to run the App.

Now, the Update and Delete functionality is required.

  • Update

The update method performs the same as create but with movie_id.

  • Delete

In Hive, the delete operation has some variations, such as:

  • w.r.t index
  • w.r.t key
  • All iterations and so on.

For this, we shall use the index approach.

box.deleteAt(index);

And we’re good at going.

Video Preview

That’s all. Thanks for your valuable time.


Conclusion

In this blog post, Interesting Concept of Hive Database for Flutter, we got to see some interesting facts, vis-à-vis went through some code snippets, and key suggestions in between. However, if you feel something is missed or have any questions regarding the topic, feel free to jot it down in the comments section. I would be more than happy to help you.

  • Check out my YouTube handle where I upload content related to Flutter itself and Github.

P.S. Check out my previous blog posts

Leave a Comment