Hive in Flutter: Local Database and Offline Caching

Flutter

Hive in Flutter is a lightweight, fast key–value database that runs purely in Dart. It’s ideal for local data such as offline caches, small databases, and user-created content that should survive app restarts. In a broader storage strategy, Hive sits between simple preferences and secure storage—as outlined in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.

Audience: IntermediateTested on: Flutter 3.x, Dart 3.x, Android 14 / iOS 17, macOS 14

Where Hive fits in your Flutter app

Hive is best suited for:

  • Offline caches: storing API results, timelines, and lists for quick reloads.
  • Local “mini database”: todo items, notes, or domain models that don’t need a full SQL engine.
  • Small config tables: feature flags, lookup data, recent items.

It is not a replacement for secure storage. Sensitive secrets (tokens, keys, PINs) still belong in something like flutter_secure_storage, as discussed in the comparison article above.

Installing Hive

Start with the core packages. For mobile/desktop you also need path_provider to choose a storage directory. For typed objects you’ll use code generation via hive_generator.

# pubspec.yaml
dependencies:
  hive: ^latest
  path_provider: ^latest

dev_dependencies:
  hive_generator: ^latest
  build_runner: ^latest

Full documentation lives on Hive’s pub.dev page, but the following setup works for most apps.

Defining a Hive model and adapter

Hive stores data in boxes. Boxes can contain primitive values (strings, ints, maps) or custom objects. For objects, you define a class and generate a type adapter.

// lib/models/todo.dart
import 'package:hive/hive.dart';

part 'todo.g.dart';

@HiveType(typeId: 1)
class Todo extends HiveObject {
  @HiveField(0)
  String title;

  @HiveField(1)
  bool done;

  Todo({required this.title, this.done = false});
}

Generate the adapter:

dart run build_runner build --delete-conflicting-outputs

This creates todo.g.dart with a TodoAdapter Hive can use for serialization.

Initializing Hive on app startup

Initialize Hive once in main(). On mobile/desktop you typically store data under the app documents directory.

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

import 'models/todo.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final dir = await getApplicationDocumentsDirectory();
  Hive
    ..init(dir.path)
    ..registerAdapter(TodoAdapter()); // from todo.g.dart

  // Open boxes before runApp, or lazily later.
  await Hive.openBox<Todo>('todos');

  runApp(const MyApp());
}

Basic CRUD with boxes

Once a box is open, you can treat it like a small database. The simplest pattern is to wrap it in a repository class rather than scattering box calls throughout the UI.

// lib/repositories/todo_repository.dart
import 'package:hive/hive.dart';
import '../models/todo.dart';

class TodoRepository {
  TodoRepository(this._box);
  final Box<Todo> _box;

  List<Todo> getAll() => _box.values.toList();

  Future<void> add(String title) async {
    await _box.add(Todo(title: title));
  }

  Future<void> toggleDone(Todo todo) async {
    todo.done = !todo.done;
    await todo.save(); // HiveObject method
  }

  Future<void> delete(Todo todo) async {
    await todo.delete();
  }
}

In your widget tree, inject TodoRepository via Provider/Riverpod/GetIt and rebuild when box contents change. Hive supports reactive patterns via ValueListenable on boxes.

Listening to changes (for reactive UIs)

Boxes expose a listenable() method that tells Flutter when something changes, which is perfect for small apps without a full state management library.

// Inside a widget
@override
Widget build(BuildContext context) {
  final box = Hive.box<Todo>('todos');
  return ValueListenableBuilder(
    valueListenable: box.listenable(),
    builder: (context, Box<Todo> box, _) {
      final items = box.values.toList();
      return ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          final todo = items[index];
          return CheckboxListTile(
            value: todo.done,
            title: Text(todo.title),
            onChanged: (_) => todo
              ..done = !todo.done
              ..save(),
          );
        },
      );
    },
  );
}

Encrypting a Hive box

For moderately sensitive data, Hive supports box-level encryption using a 256-bit key. The key itself should be generated once and stored in secure storage, not hard-coded.

// Generate a key once (do this in a setup script or first-run logic)
final key = Hive.generateSecureKey(); // 32 bytes
print(key); // store securely, e.g. via flutter_secure_storage
// Opening an encrypted box
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

final encryptionKey = await secureStore.readKeyBytes('hive_todos_key');

final box = await Hive.openBox<Todo>(
  'todos_secure',
  encryptionCipher: HiveAesCipher(encryptionKey),
);

Remember that encryption adds overhead. Use it only where needed, and keep truly sensitive secrets (tokens, master keys) in secure storage itself.

Migrations and schema changes

Because Hive uses numbered fields (@HiveField), you can evolve models without breaking existing data if you follow a few rules:

  • Never reuse field numbers. Once a field ID is used, keep it reserved even if you remove the property later.
  • Use nullable fields or defaults. When adding new fields, give them sensible defaults or make them optional.
  • Write one-time migration code when you need to transform old data (for example, combining two fields or renaming options).

Because all reads/writes pass through your repository, you can implement migrations there without touching every screen.

Testing Hive-based code

For unit tests, you can use an in-memory Hive instance or a temporary directory to keep tests isolated.

setUp(() async {
  TestWidgetsFlutterBinding.ensureInitialized();
  final dir = await Directory.systemTemp.createTemp();
  Hive
    ..init(dir.path)
    ..registerAdapter(TodoAdapter());
  await Hive.openBox<Todo>('todos');
});

tearDown(() async {
  await Hive.box<Todo>('todos').clear();
  await Hive.close();
});

With this setup, your repositories and widgets can run against real Hive boxes in tests without polluting local storage.

Security & common pitfalls

  • Storing secrets directly in Hive: Tokens and passwords do not belong in plain Hive boxes. Use secure storage, or store an encryption key securely and encrypt the box.
  • Ad-hoc access everywhere: Calling Hive.box() from random widgets makes refactors hard. Wrap access in repositories or services.
  • Forgetting to close Hive: On desktop and integration tests, call Hive.close() when the app shuts down to avoid file locks.
  • Overusing Hive for tiny flags: For a handful of booleans and strings, shared_preferences is simpler. See the broader comparison in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.

FAQ

Q: Is Hive a replacement for SQLite?

A: Not exactly. Hive is great for key–value and document-like data and is very fast for many use cases. For complex relational queries or heavy reporting, a relational database (like SQLite via drift) may still be a better fit.

Q: Can I use Hive on the web?

A: Yes. Hive supports the web by using IndexedDB under the hood. Just avoid very large payloads and test performance on target browsers.

Q: How big can a box get?

A: Hive can handle quite large boxes, but you should still paginate and avoid loading the entire dataset into memory at once. For massive datasets, consider segmenting data into multiple boxes or using a more specialized database.

Conclusion

Hive in Flutter gives you a fast, flexible local database that works well for offline caches and small to medium datasets. Combined with shared_preferences for simple settings and flutter_secure_storage for secrets—as outlined in the storage comparison article—you get a clear, layered storage strategy that keeps your app responsive and your data organized.

Updated: 2025-11-12

Comment

Copied title and URL