← Articles

Using Hive for local storage in Flutter

By Charlin Joe · 19 September 2025

Hive is a lightweight, fast key-value store for Flutter built on top of binary serialization. It runs entirely in pure Dart (no native dependencies), which makes it easy to set up and cross-platform without any SQLite installation headaches.

When to use Hive

Hive is ideal when:

  • You need fast read/write of non-relational data
  • You're caching API responses or user-generated content offline
  • You want reactivity without setting up a SQL schema
  • You don't need complex queries (JOINs, aggregations)

For complex relational queries, prefer drift. For encrypted storage of secrets, prefer flutter_secure_storage.

Setup

dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0

dev_dependencies:
  hive_generator: ^2.0.1
  build_runner: ^2.4.0

Initialization

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  Hive.registerAdapter(ProductAdapter());
  Hive.registerAdapter(CartItemAdapter());
  await Hive.openBox<Product>('products');
  await Hive.openBox<CartItem>('cart');
  runApp(const MyApp());
}

Defining data types

import 'package:hive/hive.dart';

part 'product.g.dart';

@HiveType(typeId: 0) // Unique ID across ALL types in your app
class Product extends HiveObject {
  @HiveField(0) late String id;
  @HiveField(1) late String name;
  @HiveField(2) late double price;
  @HiveField(3) late String imageUrl;
  @HiveField(4) late DateTime updatedAt;

  Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
    required this.updatedAt,
  });
}

Generate the adapter:

flutter pub run build_runner build --delete-conflicting-outputs

Reading and writing

final box = Hive.box<Product>('products');

// Write
await box.put('prod-1', Product(
  id: 'prod-1',
  name: 'Blue Widget',
  price: 9.99,
  imageUrl: 'https://...',
  updatedAt: DateTime.now(),
));

// Bulk write
await box.putAll({'prod-2': product2, 'prod-3': product3});

// Read
final product = box.get('prod-1');  // null if not found
final allProducts = box.values.toList();

// Delete
await box.delete('prod-1');
await box.clear(); // Delete all

Auto-increment keys

await box.add(product); // Returns the integer key
await box.addAll([p1, p2, p3]);

final first = box.getAt(0);
await box.deleteAt(0);

HiveObject convenience methods

When your type extends HiveObject, you get save/delete directly on the object:

final product = box.get('prod-1')!;
product.price = 8.99;
await product.save(); // Updates in box automatically
await product.delete();

Reactive UI with ValueListenableBuilder

class ProductListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<Box<Product>>(
      valueListenable: Hive.box<Product>('products').listenable(),
      builder: (context, box, _) {
        final products = box.values.toList();
        if (products.isEmpty) {
          return const Center(child: Text('No products'));
        }
        return ListView.builder(
          itemCount: products.length,
          itemBuilder: (_, i) => ProductCard(product: products[i]),
        );
      },
    );
  }
}

Listen to specific keys only:

Hive.box<Product>('products').listenable(keys: ['prod-1', 'prod-2'])

Encrypted boxes

Hive supports AES-256 encryption. Store the encryption key in flutter_secure_storage:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'dart:convert';

const storage = FlutterSecureStorage();

Future<Box> openEncryptedBox(String name) async {
  var keyString = await storage.read(key: 'hive_key');
  late List<int> encryptionKey;

  if (keyString == null) {
    encryptionKey = Hive.generateSecureKey();
    await storage.write(key: 'hive_key', value: base64Url.encode(encryptionKey));
  } else {
    encryptionKey = base64Url.decode(keyString);
  }

  return Hive.openBox(name, encryptionCipher: HiveAesCipher(encryptionKey));
}

Lazy boxes for large datasets

Regular boxes load all values into memory at open. Use LazyBox for large datasets:

final lazyBox = await Hive.openLazyBox<Product>('large_catalog');
final product = await lazyBox.get('prod-1'); // Reads from disk on demand

Testing

setUp(() async {
  Hive.init(Directory.systemTemp.path);
  Hive.registerAdapter(ProductAdapter());
  await Hive.openBox<Product>('products');
});

tearDown(() async {
  await Hive.deleteFromDisk();
});

Common pitfalls

Duplicate typeId. Every @HiveType(typeId: N) must be unique across your entire app. Duplicate IDs cause data corruption. Keep a comment somewhere listing all used IDs.

Changing field numbers. Once a type is written to disk, changing @HiveField(N) numbers makes existing data unreadable. You can add new fields with new numbers, but never change existing ones.

Not closing boxes before re-opening. Opening the same box twice without closing throws an error. In tests, Hive.deleteFromDisk() in tearDown gives you a clean slate.

Sign in to like, dislike, or report.

Using Hive for local storage in Flutter — ANN Tech