Logo

The Unbound Academy

Flutter Multi-Platform Development

Flutter Hive Local Data Storage

Flutter
Riverpod
Riverpod

Introduction

Step 1: Add Dependencies

Open your pubspec.yaml file and add the following dependencies:

dependencies:
  hive: ^2.2.3

dev_dependencies:
  hive_generator: ^2.0.0
  build_runner: ^2.3.3
    

Run:

flutter pub get

Step 2: Initialize Hive

Import Hive and a path provider for directory management. Initialize Hive in your main.dart file:

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final directory = await getApplicationDocumentsDirectory();
  Hive.init(directory.path); // Initialize Hive
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HomePage());
  }
}
    

Step 3: Open a Hive Box

A Hive "box" is like a small database. Open a box to store data:

final box = await Hive.openBox('myBox');

Step 4: Add, Get, and Delete Data

Hive stores data as key-value pairs. Here's how to perform basic operations:

Add Data

await box.put('username', 'JohnDoe'); // Save "JohnDoe" with key "username"

Get Data

String username = box.get('username'); // Retrieve data using the key
print(username); // Output: JohnDoe

Delete Data

await box.delete('username'); // Remove data associated with the key

Step 5: Use Hive in a Flutter App

Example of using Hive in a simple counter app:

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final directory = await getApplicationDocumentsDirectory();
  Hive.init(directory.path);
  await Hive.openBox('counterBox'); // Open box before using it
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterPage());
  }
}

class CounterPage extends StatelessWidget {
  final box = Hive.box('counterBox'); // Access the box

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hive Counter')),
      body: Center(
        child: ValueListenableBuilder(
          valueListenable: box.listenable(), // React to changes
          builder: (context, box, _) {
            final counter = box.get('counter', defaultValue: 0);
            return Text('Counter: $counter', style: TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          final currentCounter = box.get('counter', defaultValue: 0);
          box.put('counter', currentCounter + 1); // Increment counter
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
    

Step 6: Close the Box

Always close Hive when the app is terminated:

@override
void dispose() {
  Hive.close();
  super.dispose();
}

Custom Models in Hive

Step 1: Create a Custom Model

Define a Dart class to represent your data. For example, a User model:

class User {
  final String name;
  final int age;

  User({required this.name, required this.age});
}

Step 2: Annotate the Model

Hive uses code generation to create adapters. To enable this, annotate your model with @HiveType and its fields with @HiveField.

import 'package:hive/hive.dart';

part 'user.g.dart'; // Required for code generation

@HiveType(typeId: 0) // Unique ID for this model
class User {
  @HiveField(0) // Field index, must be unique within the class
  final String name;

  @HiveField(1)
  final int age;

  User({required this.name, required this.age});
}

Step 3: Generate the Adapter

Run the following command to generate the adapter:

flutter pub run build_runner build

This creates a user.g.dart file with a UserAdapter class.

Step 4: Register the Adapter

Register the adapter with Hive before using it:

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';

import 'user.dart'; // Import the model
import 'user.g.dart'; // Import the generated adapter

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

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

  Hive.registerAdapter(UserAdapter()); // Register the adapter

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HomePage());
  }
}

Step 5: Use the Model in Hive

Save a Custom Object

final box = await Hive.openBox('userBox');
final user = User(name: 'John Doe', age: 25);

await box.put('user1', user); // Save the user object

Retrieve a Custom Object

final user = box.get('user1') as User;
print('Name: ${user.name}, Age: ${user.age}');

Delete a Custom Object

await box.delete('user1');

Understanding the Adapter

The adapter handles:

  • Serialization: Converts the object into a format Hive can store.
  • Deserialization: Converts the stored data back into an object.

Example of an auto-generated adapter (user.g.dart):

class UserAdapter extends TypeAdapter<User> {
  @override
  final int typeId = 0;

  @override
  User read(BinaryReader reader) {
    final name = reader.readString();
    final age = reader.readInt();
    return User(name: name, age: age);
  }

  @override
  void write(BinaryWriter writer, User obj) {
    writer.writeString(obj.name);
    writer.writeInt(obj.age);
  }
}

Benefits of Using Custom Models and Adapters

  • Organized Code: Models make it easier to work with structured data.
  • Performance: Hive uses binary storage, making it fast.
  • Flexibility: You can easily update your models and manage complex data relationships.

Encryption in Hive

Step 1: Generate an Encryption Key

You need a 256-bit encryption key. You can generate it programmatically using the crypto package.


import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';

Uint8List generateEncryptionKey() {
final key = sha256.convert(utf8.encode('your-secret-key')).bytes;
return Uint8List.fromList(key);
}

final encryptionKey = generateEncryptionKey(); // Use this key for encryption

Alternatively, you can use Random.secure() for a random key:


import 'dart:math';
import 'dart:typed_data';

Uint8List generateEncryptionKey() {
return Uint8List.fromList(List.generate(32, (_) => Random.secure().nextInt(256)));
}

Step 2: Open an Encrypted Box

When opening a box, pass the encryption key to Hive’s HiveAesCipher.


import 'package:hive/hive.dart';
import 'dart:typed_data';

void main() async {
final encryptionKey = generateEncryptionKey();

// Initialize Hive
final directory = await getApplicationDocumentsDirectory();
Hive.init(directory.path);

// Open an encrypted box
var box = await Hive.openBox(
'secureBox',
encryptionCipher: HiveAesCipher(encryptionKey),
);

// Save and retrieve data
await box.put('key', 'secureData');
print(box.get('key')); // Outputs: secureData
}

Step 3: Save the Encryption Key Securely

Since the encryption key is essential for decrypting the data, never hardcode it in your app. Instead, store it securely:

  • Use Secure Storage (e.g., flutter_secure_storage) to store the key.
  • Retrieve the key at runtime when needed.

Example:


import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final secureStorage = FlutterSecureStorage();

Future<void> storeEncryptionKey(Uint8List key) async {
await secureStorage.write(key: 'hiveKey', value: base64UrlEncode(key));
}

Future<Uint8List> getEncryptionKey() async {
final key = await secureStorage.read(key: 'hiveKey');
return base64Url.decode(key!);
}

Advantages of Hive Encryption

  • AES-256 Security: Industry-standard encryption.
  • Easy Implementation: Built into Hive's API.
  • Full-Box Encryption: Encrypts all data in a box.

Important Notes

  • If you lose the encryption key, the data in the box cannot be recovered.
  • For sensitive apps, always store the encryption key in a secure location.
  • Use encrypted boxes only when necessary since encryption may slightly impact performance.